• (cs) in reply to RaspenJho

    RaspenJho wrote: Someone had to do it....

    Oh goodie! Now we've taken the perfectly comprehensible

    private void attachSupplementalDocuments()
    {
      if (stateCode == "AZ" || stateCode == "TX") {
        //SR008-04X/I are always required in these states
        attachDocument("SR008-04X");
        attachDocument("SR008-04XI");
      }
    
      if (ledgerAmnt >= 500000) {
        //Ledger of 500K or more requires AUTHLDG-1A
        attachDocument("AUTHLDG-1A");
      }
    
      if (coInsuredCount >= 5  && orgStatusCode != "CORP") {
        //Non-CORP orgs with 5 or more co-ins require AUTHCNS-1A
        attachDocument("AUTHCNS-1A");
      }
    }
    

    and turned it into XML... which ain't so comprehensible...

    <SUPPLEMENTAL_DOCUMENTS>
      <CONDITION TYPE="stateCode" VALUE="AZ" OPERATION="=">
        <ATTACH DOCUMENT="SR008-04X" />
        <ATTACH DOCUMENT="SR008-04XI" />
      </CONDITION>
    
      <CONDITION TYPE="stateCode" VALUE="TX" OPERATION="=">
        <ATTACH DOCUMENT="SR008-04X" />
        <ATTACH DOCUMENT="SR008-04XI" />
      </CONDITION>
      
      <CONDITION TYPE="ledgerAmnt" VALUE="500000" OPERATION=">=">
        <ATTACH DOCUMENT="AUTHLDG-1A" />
      </CONDITION>
      
      <MULTICONDITION>
        <CONDITIONS OPERATION="AND">
          <CONDITION TYPE="coInsuredCount" VALUE="5" OPERATION=">=" />
          <CONDITION TYPE="orgStatusCode" VALUE="CORP" OPERATION="!=" />
        </CONDITIONS>
        <ATTACHMENTS>
          <ATTACH DOCUMENT="AUTHCNS-1A" />
        </ATTACHMENTS>
      </MULTICONDITION>
    </SUPPLEMENTAL_DOCUMENTS>
    

    ... and we've moved those pesky hardCoded values to a hardcoded mapping method ...

      object value =
        cond.Type == "stateCode"      ? data.StateCode      :
        cond.Type == "ledgerAmnt"     ? data.LedgerAmnt     :
        cond.Type == "coInsuredCount" ? data.CoInsuredCount	:
        cond.Type == "orgStatusCode"  ? data.OrgStatusCode	:
        string.Empty;
    

    Wonderbar! All we need to do know is write our own XML rules-mapping code-generator! ... and of course we'll need an rules-editor application to make that "XML programming language" look more like Java.

    This way lies madness.... Please don't do it, unless you have a requirement to do so, such as maintaining a history of the business-logic, allowing you to repeat a particular process as-per the rules which where in effect at that time.

    Signed: The humble maintenance programmer.

    Cheers. Keith.

  • David (unregistered)

    Hello there,

    my colleague always says: "I good developer is a lazy developer". Sure thing from my point of view.

    Two examples from two different customers:

    1. Customer: "everything has to be parameterizable within the database"- so we coded our solution that way.

    First deployment: July 2002 First test if the did it right (customer changing some button texts and stuff): August 2002 Since then? Nothing- a lot of work (development and testing) for nothing- so much for mandatory configuration gg

    1. Customer: "We want to change the labels and translate them into different languages". Our developers did their best and we ended up in about 2000 lines of xml-config. Guess what- the customer was not able to alter the right entries and therefore we are changing translations and including them into subsequent releases of the software.

    The point is- depending on the customer and the size of your project- be reasonable and do not use config because "software is done that way nowadays"- it isn't. Software ist supposed to solve problems, kept simple and mainly cheap (minor changes causing tons of money to be spent will tend to disappear- so there are no changes at the end of the day).

    Why do we have that much frameworks, tools and scripting languages? Because writing software without something exciting and new is not interesting for the usual developer ("been there, done that")- so as long as you have this "proud" thing within development ("sure I can use O/R-Mapper X, but I need this and that and can code it in the same amount of time, I have to learn the API"), there won't be any substantial progress.

    We are defying the same programming and design errors as our fathers did in the 70s, don't you think so?

    At least I have no solution for this- maybe there is not (at least no deterministic ;-))).

    cheers David

  • golddog (unregistered)

    I think what Alex is strying to say is a good thing to keep in mind when developing: don't solve problems that don't exist.

    For the example he shows, sure, when another state comes on, or we need to change the document type for one state, or something, we might revisit this process and rewrite it. Maybe we configure a list of states that need 048X and 048XI and check against that.

    But, the fact is, these are the requirements. Don't make things unnecessarily complicated when there's not a legitimate reason to.

    Some of the examples remind me of a developer at a previous employer who wanted to make everything "enterprisy". We used to joke that he would argue we'll probably colonize Mars some day, and since the Martian day is 39.5 minutes longer than the Earth day, we should allow for that difference in our code now. (Not as far from some of his arguments as you might think).

    The point is, you make you best guess at the likelihood of change, rather than blindly add complexity. If you find later that these conditions really are pretty dynamic, it's just software, go ahead and refactor at that point.

    It's great to anticipate potential problems, but don't assume complexity where there really isn't likely to be any.

  • mzamora (unregistered)

    I haven't read the five pages of comment (and I won't), but I'm just going to add how I see the whole hard vs. soft coding through my experience with pretty complicated software: financial services software that encode how to do business with all kinds of weird financial instruments, which are full of arbitrary rules and exceptions, which nevertheless can (mostly) be represented as soft code.

    Hard code is OK when the rules are few, they don't change frequently, and the process is OK with involving the developer whenever a rule has to change.

    Soft code should be used when any of the above conditions can't be met.

    For example, picture the little 300-line hack evolved into the 50-file, 35,000-line monster in the course of four years. In this case, hard code is the main cause of bugs and broken business processes that cause 2:00AM calls to wake the comatose developer sleeping under the desk.

    When the business rules in the application reach a certain level of complexity, a new level of abstraction can generally be applied to organize the rules. Then it's time to dump the old app --or the parts of the app that encode those rules-- after developing a new way to soft code the business rules which makes it more maintainable.

    90% of the cases can be represented with soft coding without resorting to generalized data structures which are so over-designed that they turn into abstract exercises in intellectual onanism.

    The really weird cases must be relegated to hard code, and if possible, should be done with code running in a sandbox that accesses the business data through a strictly defined interface.

    Perl comes to mind (using anonymous closures), EJBs, Java stored procedures, etc.

    In a nutshell, soft code can take care of the huge majority of rules in an efficient, maintainable manner. Hard code should be used with the exceptions, but that hard code should be isolated both in the build process and at run time, and closely tested and monitored (unit testing, sandboxing, etc.) to ensure correctness because they will be the source of most of the insidious bugs.

  • veggen (unregistered)

    Ummm... There's really no "soft/hard code" dilemma here. Have you heard of Drools? Jess? There are times when you can't avoid changing the code, but this is not one of them.

  • da nigga rie chea (unregistered) in reply to joe_bruin

    does zero really exist? does seven even exist? let me check my bible. . .

  • c2d (unregistered) in reply to cparker

    Actually your comment proves the exact opposite. If there are several definitions for BILLION, using it as a constant would surely create confusion. Consider x = 60 * ONE_BILLION, what's the answer? Is it 60 000 000 000 or 60 000 000 000 000?

    Now consider x = 60 * 1 000 000 000. What's the answer here?

    So whatever you call that number, we can agree upon it's numeral expression. But if you used a define as suggested, we may expect entirely different answers and just end up talking past each other.

  • oksupra.com (unregistered)

    Supra shoes are so popular all over the world. Whatever you take on Supra being in the Supra Skytop Shoes market. Now Supra Strapped Shoes also very popular attention. It is nice that they actually took the time to make Supra Skate Shoes that work well. Supra shoes is a brand that has been inspired, designed and marketed by passionate individuals. We have brought to you the fullest selection of Supra footwear at cheapest price.

  • xiaokj (unregistered)

    Reading the scary full 5 pages, it is quite weird to see that Alex had been mostly absent from this forum debate. I hope to see a follow up.

    I belong heavily in the soft-coding side, but I agree that this is in the grey area up for debate. Neither end is worth pursuing to their logical conclusions.

    I think it is better to think of development methodologies. Agile methods would prefer hard-coding prototypes, and when the need arises, expand it into soft-code. Some other methods would argue for the other direction.

    Although I would very much prefer soft-coding, I can see where the Agile method is hinting at, and I agree with that. However, I will only agree with the prototyping phase (or project is too small to warrant anything other than a prototype). Once the underlying method is working, I think I would rather see it soft-coded, albeit just a little bit.

    After this huge amount of unsubstantiated opinion, let me add some meat to my argument.

    If you see soft-coding as optimisation in the rule modification process, then you can clearly see how unwarranted huge architectures in soft-coding is premature optimisation. It is more important to make sure that your internal code structure works before you think of constructing your soft-coding behemoth.

    Take the case of sendmail. It was born in a time when UUCP was the norm and each pre-network had its own mail syntax (the @ symbol was yet to be). As such, it had to soft-code the network characteristics in its configuration file or hard-code hundreds of cases. Common sense mandated the former, and culminated in the configuration mess. Later, it would be the same configuration mess that would doom the project against rivals -- the @ sign just gained traction.

    However, if sendmail were not extensible, the internet would have had trouble forming because mail would not traverse borders. Not to mention that its extensibility allowed for experimentation with design, finally leading to the @ sign standard. The lesson to get is not that soft-coding is bad, but rather that extensibility needs taming, and that standardisation is important.

    On the other end of the spectrum, look into the Art of Unix Programming Chapter 9 on Data Driven Programming. The preceding introductory chapters talked about complexity -- humans have trouble with complex algorithms, but are good at handling complex data structures. When the correct data format appears, the problems at coding solve themselves. Then, as promised, it expands on the idea in Chapter 9 with case studies, 2 of concern to the problem at hand are ascii and fetchmail.

    Ascii is undoubtedly the most beautiful representation of simplicity in algorithm and code, yet it is entirely soft-coded. And the soft-coded part is entirely compiled and hard-coded into the binary -- the scope for changes is low enough to warrant that.

    Fetchmail example, on the other hand, talks about using formal parsers to handle configuration, and the implementation genius is to, instead of copying another instance for the configuration-helper, use the nicely created parser for the main program to dump the parsed config for the helper. Centralised nice code without increasing complexity much.

    It is clear that both sides are capable of monstrosities. Sendmail is a real disaster in its early days, but at least the disaster is mandated by necessity. However, it does not mean that soft-coding is inherently more complex than hard-coding -- ascii shows how the opposite can be the case, given clever coding.

    I suppose we need to agree, on the other hand, that the example stated by Alex is horrendous. State laws are notorious to political whims and hence tend to be messy. If each state has 2 random demands, 20+(? I am not American, and I cannot be bothered to research minor details) states will mean 40+ overlapping conditions to code for. Slow-moving regulations will make it horrendous to push out changes in hard-coded values. A more sensible approach would be to cleanly separate out the state laws and allow for specialists to formally produce the configurations required, and send that out. Following the ascii example, it can be compiled, albeit into a module/internal state instead of the whole program. Flexibility as is required can be arranged for. Making it part of a database would, in this case, be uncalled for.

    Nevertheless, the underlying idea that there is a limit to soft-coding is correct. Sendmail is my replacement example. Too much flexibility is also trouble for testing. Each binary yes/no option doubles the testing load -- exponential increase so that 10 options creates additional 1024 test cases!

    As many had already said, we need some form of balance, and I suppose hard-coding a prototype is more than justified. If the prototype is more than satisfactory, there will be no need to soft-code. However, if some internal and variable threshold is overcome, soft-coding is much better. Especially when you find yourself repetitively apply fixes -- the codebase that needs repetitive fixes is more likely the source of the problem itself.

    Now for some of my own attempts at the forum digression.

    #DEFINE DAYSinWEEK 7 is horrendous-looking, but will save you when you realise that you actually meant WorkDaysInWeek 5 or 5.5 -- a global search and replace DAYSinWEEK to WORKdaysINweek is a lot easier than looking through for 7s all over the place. Happened to me.

    #DEFINE SEC_IN_MIN 60 is probably sadder than what I propose: seconds = minutes * 60 // 60 seconds in a minute i.e. I propose comments for this purpose -- constants extremely unlikely to change at all. This is because you might want to do it with undecipherable variable names as shown above with the object-orientated code -- suddenly you cannot even see that you are dealing with seconds and minutes when you have to navigate through the object structure.

    MILLION and BILLION should be 1E6 and so on, with comments on why you are using them that way, as in the case above.

  • x-sol (unregistered)

    The Enterprise Rules Engine Revisited

    Why does the word RETS keep going through my head?

  • DriverDevel (unregistered) in reply to Cotillion
    Cotillion:
    Always program as if the person who will be maintaining your program is a violent psychopath that knows where you live.

    If you obfuscate your code to its fullest, then he'll never be able to figure you out anyway ;)

  • (cs)

    One extreme version of this (or perhaps a separate antipattern) is localization.

    Instead of the perfectly clear

    printf("stop\n");

    you get printf(__(STR732));

    (I'm not kidding. that was the standard at a certain fortune500 company).

    whenever possible I try to get 'stuck' with the job of localization so I can do the obvious thing - write a little preprocessor that replaces "stop\n" with "alto\n" driven by a file. I've heard the objection that people might want to translate 'stop' into different words by context - in which case make it

    printf("stopTHINGTOKEEPDOORFROMSWINGING\n"); and printf("stopPUTONTHEBRAKES\n");

    and run the american english version through a 'translation' as well.

    There's a general tendency for software engineers to be 'pound wise and penny foolish' - looking for optimizations in large and complex cases like needing to change a constant in context while ignoring simple needs like the need for the programmers to understand the code.

  • Anonymous (unregistered) in reply to Isuwen

    That's actually a really good example.

    The simple scripting language in Steam Source games might not even be turing-complete, but the scripts allow a wide variety of customization.

    Want to make the game entirely playable without a mouse?

    bind j +left
    bind l +right
    bind i +lookup
    bind k +lookdown
    etc...
    

    Imagine if this philosophy were used for stuff like text editors.

    "Hmm, I use double-underline bold font a lot..."

    bind "ctrl shift d" "toggle-bold, toggle-underline"
    

    Yes, scripts are ''very'' useful for this sort of thing. They let you change not only a few constants, but the course of the entire program, just by editing a few text files.

  • Anonymous (unregistered) in reply to Glazius

    GOF was, and never will be a WTF.

    The fact that half of them would be unnecessary with a decent metaclass or prototype system is a WTF.

    More related to your comment, the fact that everyone ignores the "drawbacks" section when deciding whether or not to use a GOF pattern, is a major WTF.

    Something tells me that IDocumentAttachmentStrategy's implementors would take up way more LoC than a simple if/else chain.

    And it still wouldn't be user-customizable. Unless they had a decompiler.

  • JackSchitt (unregistered)

    I've had to do something like this previously. The gotcha was that the business logic HAD to be in a separate file (as per the spec) which would be periodically updated by some manager separate from the deployment of the app.

    The 1.0 release used windows scripting host. The .rules file was basically a .vbs file with functions that would be called by the app.

    They decided (after something like 10 years of use) that editing rules in notepad was unintuitive and error prone. I took this time to migrate everything to .Net and now the .rules files are compiled the first time they are needed. The best part is that you can write the files in the VS IDE with intellisense.

    It works, and it works well. If a rule needs some piece of information not provided by the standard params for that function, it can access the database as needed. There was even one case where a rule would consult an email inbox to determine it's course of action (it was for a special promotion where you had to email the company to take part).

  • Susan (unregistered) in reply to joe_bruin

    Yeah, but this is missing the point. I'd prefer to read: if (x < NUM_DAYS_IN_WEEK)

    than if (x < 7)

    because it explains the 7.

    You don't just have constants so you can change them, or to help you remember the value. It answers the question "WTF 7?".

  • Powerslave (unregistered)

    Yup. And I surely want to rebuild 40k+ file application just to change which particular document is linked with a given state or amount... So-called configuration files did not emerge without a good reason. You just need to find the correct balance.

  • (cs)

    This is interesting article

  • Nael Mahfouz (unregistered)

    I liked your article very much. I disagree with nearly everything you have said but I think the article shows a lot of evenings trying to figure out bad soft coding. The example you have provided is very simple and I agree that creating some form of business enterprise rules engine to handle this is overkill. Your final paragraph is the ultimate in soft-coding. It is all about using metadata to reduce complexity, regression testing and code fragility to programmatically compile solutions.
    There are intermediate solutions as well. Any code element executes an input, processing and an output. If you factorise out the common denominators of these code elements in any given piece of work, normally there are only a few elements. If sensible rules-driven metadata are used to describe the input, operation and output of the handful of code activities then the code remains relatively static (until new functionality is required) while the metadata changes need only unit/integration testing and automatically regression tests the code. Finally, "soft-coding" allows you to reduce the amount of development from the product of variables to the sum of variables. If there are ten code elements with three operations each, there are thirty portions of code. With softcoding, there are only three pieces of code and ten pieces of metadata equalling 13 instead of 30 development activities. When you multiply this by several hundred times, the savings become enormous and the quality of the soft-coded codebase becomes impressive.

    Where soft-coding falls down is lack of analysis and the use of convoluted and difficult to understand metadata. This is what I believe you are describing. But in this case the map is not the territory.

  • (cs)

    Brrrains

    (for Akismet, spamming and pointless-thread-rezzing are the same, maybe ?)(if so, -1 shame point for this rotten pile of sh... brilliant software.)

  • Ross Presser (unregistered) in reply to ssprencel

    The advantage of sticking by rules like "Everything in moderation" is that you get to point your finger at anybody you like, saying they're being immoderate one way or another, without having to back up your statements.

  • TJ Powell (unregistered) in reply to jonny lech

    Speaking as a requirements expert - that would indicate that the "requirements" were simply not well-thought-out and written correctly to begin with. The current popular paradigm is start coding as quickly as possible, and change things as details emerge. Emergent "engineering". This is like building a house without a firm, well-constructed blue print - and leads to the same result. Chaos.

  • TJ Powell (unregistered) in reply to Ross Presser

    It means, common sense must be deployed with respect to the amount of abstraction. Benefits and expense must be weighed in each case. Sometimes, when reuse is called for - a higher level of abstraction and runtime configurability makes sense. Most often, it is easier to simply write the code. I've worked on EXTREMELY complex space systems - and almost all the code is done inline. For a reason. Because a person can immediately look at the code and determine what it does. Sustaining engineering costs are always higher than original development. So that is where the emphasis in large systems should be. Far above any concern for "elegance" - which is more often than not, a euphemism for convolution. I still believe that many programs enjoy writing code that others have difficulty understanding. I attribute that to an ego malfunction.

  • TJ Powell (unregistered) in reply to TJ Powell

    And I will further comment that most "interfaces" as currently defined, are often useless and indeed, confusing. The assumption of an immutable interface, where only the code changes MIGHT work if the system was well-thought-out, and planned to last for many decades in terms of reuse. But in software, this is almost NEVER the case. I've made a living coming in and cleaning up interfaces that were broken. Broken by inevitable change. (And I'm talking about repeated changes to the interface signatures themselves - See Microsoft). I get paid lots and lots of money to come in and clean up code written by developers who prize academics over productivity. At the end of the day, I actually get paid to throw out lots of unnecessarily abstract code - and write code that is maintainable, meets the requirements and eliminates crazy levels of complexity. Many of the "modern" paradigms have been created in academia by folks trying to sell books, and marketing departments trying to create a "brand".

  • TJ Powell (unregistered) in reply to TJ Powell

    I define my state machines and transitions in a global module, in HARD CODE (so to speak). Not in a config file or database. If the rules change, I rewrite that piece of code and republish. Most of the time the users don't even see the change. It takes 20 minutes to rewrite, test and publish. And everyone who works on the system knows exactly where to go to find the data structure (one!) that defines the legal transitions. Since it is shared code, and it not splattered all over the database, it's simple - not accessible to the user, and we have readable, controllable, affordable and simple code to maintain. That is why one person (me) can write 800K lines of commercial code in less than 3 years, and sell it at a profit.

  • UnkownDev (unregistered)

    What I tend to do is hard code stuff that's unlikely to change. When I'm not sure if it's unlikely to change or when it's not easily understand it I just write a little comment.

    Unless I use it several times, then I create a variable with a meaningful name.

    I tend to put values into variables instead of soft coding, figuring I will have to change the code anyway but by using variables it's quick and easy.

    Soft coding is a great idea when applcicable, e.g. the amount of orders needed for a customer to get bonus points on the other hand things like statecode "AZ" is not likely to change in a long time, so you could just hard code it.

    However, things like laws are a grey zone because laws tend to change.

  • John C (unregistered)

    This article, seems to me, to be mostly nonsense. If you have a DB, you can create one generic table that has a ElementID in it so that you can softcode all sorts of values that otherwise require coding changes. You can even build a maintenance program to allow admin users to update these values.

    Our company has been using this method for decades now, and it continues to be a valid, flexible technique that is good for customers/users & programmers. It indeed, significantly reduces the need for programming changes and additional deployments.

    I hope nobody follows this advice.

  • ThunderGr (unregistered) in reply to Dwayne

    Putting numbers into constants it is, still, hardcoding. Constants are meant to provide readability to the code and make it easy to change a value used in several places in the code, without missing any.

    Softcoding, as described in this article, is moving the values from the source code to an external medium(like a file or a database) that will be read from to a variable.

  • Rob Martins (unregistered)

    Argh! I think there is a very careful set of decisions to be made around the creation of supporting sub-systems that handle logic, but the basic strategy is sound and part of an established philosophy of separating concerns.

    As a Chief Architect I abhor the notion of cramming everything into one system. Stratify and give clarity to the purpose(s) of the system. We do it all the time. We have a presentation layer, a data layer, a business layer. Some developers can't handle separation. They think everything should be in one place. If that's the case go back to one project solutions and see how you get on. It's crazy to work that way and smacks of poor experience. I simply can't fathom why anyone would not appreciate the elegance of sub-system that has a clear, central function. This means a developer can focus on the UI or Data without worrying about other parts of the system. It also protects more sensitive or technically complicated areas.

    If the intent of a system coalesces into a distinct form, it is sensible to separate. That includes exposing common problems. Therefore if your logic is crammed into page(s) - and it is littered with IF THEN and magic numbers and order precedence is all over the place then you're doing something wrong in my book and ultimately making it harder to maintain. That code will become a mess. It might be easier to cut a first draft but you're kidding yourself in the long-term.

    So, whenever I see a mass of logic on a page I cringe (you're right) and I think to myself surely you must have considered a system that does that work for you? Surely you can see that as the system grows, that code will become a mess.

    The other factor I see daily is developers who plateau systems out because they either don't know that a supporting system could be created or they if they do, they consider it to be too abstruse to create one - they're not sure how. However, that doesn't mean it's a bad idea. It depends on what that system does in terms of supporting the development. If the end result is reduced effort in creation and maintenance of a system then it is worthwhile. That maybe be hard work but does not mean because you can't do it it's a bad idea. True some can't use the system either but again is that because it's a poor system or simply that they're not experienced to work with a formal logic sub-system. Maybe the sub-system can be made easier to consume.

    Ultimately I want a developer to simply write less code and the configuration be as easy to maintain as possible. I don't see that creating a separate system that can handle logic necessarily means these things can't be achieved.

  • Rob Martins (unregistered) in reply to John C

    I completely agree. I think it was written by someone who doesn't get architecture.

  • Daniel Tress (unregistered)

    Possibly the worst message you could give a new developer.

  • Nic Battler (unregistered) in reply to Rob Martins

    I disagree completely.

  • Mats (unregistered)

    As always there is no easy truth to always be applied blindlessly. Think about who will change the parameter, is it a developer or will the software be maintained by someone else? Will there be more than one deployment with different constant values? Then decide if the constants belong in code or in a configuration file.

  • Voice of Reason (unregistered)

    The author conflates hard coding 'named values' with configurable data, and then recommends hard coding 'magic values' rather than doing either. He then provides two decent examples of why the named values are clearer (while arguing against them), and one brain-dead example of how not to name a value (ONE_BILLION). (You don't name a value after the value, you name the value after what it is used for.)

    When you use a named value rather than a magic value, you get two primary benefits:

    1. You know the underlying meaning of the value being used, and don't need an additional comment explaining it.
    2. If the value does change, you only have to change it in one place.

    You use a configured value when you expect the value to change. You use a named value when you don't expect the value to change, but you want the people (including yourself) who have to maintain your code later to understand what the value means. You use magic values when you want to have to rummage through code and figure out which instances of 60 need to be changed because they refer to: A) The two business-month time limit associated with determining whether a payment is late, B) The number of seconds in an minute, and C) The number of minutes an order should sit in the system before being printed, because customers often call back and want to add new items to their order.

Leave a comment on “Soft Coding”

Log In or post as a guest

Replying to comment #:

« Return to Article