Please create an account to participate in the Slashdot moderation system

 



Forgot your password?
typodupeerror
×
Programming Technology

Ultra-Stable Software Design in C++? 690

null_functor asks: "I need to create an ultra-stable, crash-free application in C++. Sadly, the programming language cannot be changed due to reasons of efficiency and availability of core libraries. The application can be naturally divided into several modules, such as GUI, core data structures, a persistent object storage mechanism, a distributed communication module and several core algorithms. Basically, it allows users to crunch a god-awful amount of data over several computing nodes. The application is meant to primarily run on Linux, but should be portable to Windows without much difficulty." While there's more to this, what strategies should a developer take to insure that the resulting program is as crash-free as possible?
"I'm thinking of decoupling the modules physically so that, even if one crashes/becomes unstable (say, the distributed communication module encounters a segmentation fault, has a memory leak or a deadlock), the others remain alive, detect the error, and silently re-start the offending 'module'. Sure, there is no guarantee that the bug won't resurface in the module's new incarnation, but (I'm guessing!) it at least reduces the number of absolute system failures.

How can I actually implement such a decoupling? What tools (System V IPC/custom socket-based message-queue system/DCE/CORBA? my knowledge of options is embarrassingly trivial :-( ) would you suggest should be used? Ideally, I'd want the function call abstraction to be available just like in, say, Java RMI.

And while we are at it, are there any software _design patterns_ that specifically tackle the stability issue?"
This discussion has been archived. No new comments can be posted.

Ultra-Stable Software Design in C++?

Comments Filter:
  • by DetrimentalFiend ( 233753 ) * on Saturday February 04, 2006 @11:36PM (#14644208)
    I'd hate to say it, but you might want to SERIOUSLY consider managed code. You could build some of the parts in C++ if need to be, but doing it purely in C++ seems like a bad idea to me. You're asking for a silver bullet that just doesn't exist...but managed code is getting faster and can be pretty stable.
  • Performance? (Score:2, Insightful)

    by rjstanford ( 69735 ) on Saturday February 04, 2006 @11:41PM (#14644219) Homepage Journal
    If you're willing to compromise performance to the point that you can use CORBA for IPC, then you should be more than willing to write it in the language of your choice, within reason. C, C#, C++, Java, all are far faster than your CORBA transport.

    If you can provide more details about the specific requirements, you might get more informed responses. As it is, though, your stated goals really don't seem to add up.

    Even as stated, I would write the core in a highly tuned fashion (although C++ might not be my best choice for this), then write the GUI in the language of your choice, quite frankly. Optimise the bottlenecks (ie: your core processing) for speed, optimise everything else for maintainability and ease of development.
  • by the eric conspiracy ( 20178 ) on Saturday February 04, 2006 @11:42PM (#14644220)
    THere is no silver bullet for what you describe other than sound development practices. The best results in this area are acheived by teams who are constantly refining their processes based on lessons learned in previous software iterations.

    Bulletproof code isn't cheap, but it can be done.

  • by karmaflux ( 148909 ) on Saturday February 04, 2006 @11:50PM (#14644251)
    Your program's only as stable as the "core libraries" your company wants you to use.
  • by aeroz3 ( 306042 ) on Saturday February 04, 2006 @11:59PM (#14644280)
    I think perhaps what you REALLY mean here by stability is Fault Tolerance. It's impossible to write code that has zero defects, outside of any trivial examples. Real Code Has Real Defects. Now, as you talk about modular design and being able to restart modules, you're talking about, not stability, but fault tolerance; the ability of the application to recognize and recover from faults. For instance, you can't necessarily guarantee that the module on machine A running task B won't die, hell the computer could accidently fry, but if your application was Fault Tolerant then the application would kick off another process somewhere else on computer C to rerun job B. Stable systems aren't built necessarily by trying to write defect-free code, but by recognizing that defects will occur and architecting the system in such a way that it can recover from them. Here you need to be concerned about things like transactions, data roll-back, consistency, techniques (active vs. passive, warm vs. cold). The key thing is before you even write a LINE of this C++ code, make sure that you have a complete, comprehensive ARCHITECTURE for your application that will gracefully handle faults.
  • by jimmydevice ( 699057 ) on Sunday February 05, 2006 @12:10AM (#14644312)
    If your develop safety critical code, or anything that requires hi-rel you need to break down the application into functional testable units, with test fixtures to test each module. Then a integration test framework. You can't create a "verified" correct system with ad-hoc testing. Unless you're very good and you own the whole thing and then it's just you that knows it's right, Ya right.
    JimD.
  • by gadzook33 ( 740455 ) on Sunday February 05, 2006 @12:10AM (#14644313)
    Ah, another true believer. I work heavily in both managed and unmanaged code (c/c++/c#) hybrid solutions. In my experience, a well designed C++ program is as stable as a well designed C# program. Who cares if it "crashes" if it doesn't do what you want? The worst program is one that seems to be working but is generating invalid results. Don't let anyone convince you that C# is going to provide more reliable execution. We use C# for its nice GUIs; C++ for cross-platform portability.
  • Re:XP (Score:4, Insightful)

    by Anonymous Brave Guy ( 457657 ) on Sunday February 05, 2006 @12:19AM (#14644344)

    Extreme programming is your worst enemy on this one. If you need a system that is truly reliable, you cannot take an approach that fundamentally bases its quality controls on a finite number of tests, unless you can test absolutely every possible set of inputs your program can ever receive (legitimately or otherwise).

    Testing is good, of course, but for this sort of job, you must have a proper design, such that all components can be properly verified. (And of course, you must have a proper spec against which to verify.) The XP methodolgy is pretty much the antithesis of what's needed here.

  • by AltGrendel ( 175092 ) <ag-slashdot.exit0@us> on Sunday February 05, 2006 @12:31AM (#14644383) Homepage
    Sounds like the poor soul is in over his/her eyeballs.
  • by pjkundert ( 597719 ) on Sunday February 05, 2006 @12:42AM (#14644415) Homepage
    60,000+ lines of communications protocol and remote industrial control and telemetry code. No memory leaks, and less than 5 defects installed into production.

    The reasons? A unit test suite that implements several million test cases (mostly pseudo-random probes -- the actual test code is about 1/3 the size of the functional code). In fact, the "defects" that hit production were more "oversights"; stuff that didn't get accounted for and hence didn't get implemented.

    Just as importantly; every dynamically allocated object just got assigned to a "smart pointer" (see Boost's boost::shared_ptr implementation).

    Quite frankly, compared to any Java implementation I've seen, I can't say that "Garbage Collection" would give me anything I didn't get from smart pointers -- and I had sub-millisecond determinism, and objects that destructed precisely when the last reference to them was discarded. The only drawback: loops of self-referencing objects, which are very simple to avoid, and dead trivial if you use Boost's Weak Pointer implementation.

    We didn't have access to Boost (which I Highly Recommend using, instead of our reference counted pointer) when we first started the project, so we implemented our own Smart Pointers and Unit Testing frameworks [2y.net].

    I've since worked on "Traditional" C++ applications, and it is literally "night and day" different; trying to do raw dynamic memory allocation without reference counting smart pointers is just insane (for anything beyond the most trivial algorithm). And developing with Unit Testing feels like being beaten with a bat, with a sack tied around your head...

  • by mr_tenor ( 310787 ) on Sunday February 05, 2006 @12:45AM (#14644424)
    WTF? I love Haskell as much as the next programming-language-theory fanboy, but saying "Haskell or one of the other functional languages might be a good idea." in reply to the OP strongly suggests to me that you are just making stuff up and/o are copy/pasting things that you have read elsewhere out of context

    If not, then great! Please post some references to literature which demonstrates how what you've suggested is sane and/or possible :)
  • by Iceberg1414 ( 952051 ) on Sunday February 05, 2006 @01:00AM (#14644474)
    1. I don't mean to bash your idea, but unless your "modules" are seperated into seperate processes, any part of your code or libraries can affect any other part. Writing a multi-process system has it's own complexities and issues.

    And FYI, detecting hangs is a pretty hard problem.

    2. Like was said, heavily consider going to a VM langauge (Java, .NET), unless you are willing to invest a lot of extra time in testing, code review, analysis, etc.

    3. If you are going to use C++, consider using Smart Pointers, garbage collectors, etc, available for C++ at http://www.boost.org/ [boost.org]

    4. Test early and often, and aim for full code coverage with code coverage tools. Code you don't run is code you haven't tested. Test also with memory tools such as Bounds Checker. I beleive Linux has robust gcc-enabled tools in this area also. Throw anything and everything at your code, including strange data, bad inputs, etc.

    5. Use static code analysis if possible. You will have bugs in your code, period. Historically, this generates a lot of false positives. MS VStudio 2005 Team Edition is not free, but it may be available to you through MSDN or something. This has pretty good basic analysis of common errors available through the /analyze switch. Coverity is also a better ($$$) commercial static analysis tool.

    6. Use plenty of assertions in your code

    7. Make sure you have a good crash handler setup to generate relevant crash data and hopefully semi-automatically get it back to you.

    8. Write solid unit tests WITH your code, and make sure the code is built often /w automated running of unit tests & other tests.

  • by Duhavid ( 677874 ) on Sunday February 05, 2006 @01:21AM (#14644525)
    I read his comment as saying that C# would not guarantee
    a good result ( correctness ). And it wont.

    What the guy really needs is a great team and some decent
    process to backstop that team. Not a silver bullet.
  • by jd ( 1658 ) <imipak@yahoGINSBERGo.com minus poet> on Sunday February 05, 2006 @01:43AM (#14644579) Homepage Journal
    • Where they exist, use fault-tolerent components for interconnects. Making things fault-tolerent is tough, so re-using such stuff will simplify the task. Best of all, use stuff with a significant history behind it, because communication will be the biggest headache and bugs there will be hard to pinpoint exactly.
    • When coding, assume that anything can crash. I don't care if you use exception handling, reactive methods or a purple pizza, but you want components to be able to recover from failure (by restarting if need be) and you want anything that talks to it (and the data!) to be able to survive a loss of connection and handle the condition in a predictable way. (This may mean resending to another node, waiting for the old one to reset, buying said pizza over the Internet, whatever.)
    • Keep It Simple! The more layers, the greater the liklihood of bugs. (There are exceptions - if you're using CORBA, then the ACE ORB is heavyweight but generally considered pretty solid. That's partly because it has a decent amount of maintenance and has been around a while. I would probably not go for lesser-known ORBs, though.) The more complexity you can avoid, the more certainty you can have that the code is solid.
    • Analyze, Specify, Design, Implement, Validate. There are no "perfect" techniques to Software Engineering, but a few things generally hold up fairly well. The first of these is to keep the steps in the process as clean and methodical as practical. There will be some overlap, but in general you can't implement good code until you know what good code you want implemented.
    • Testing Is Important. There are more schools of thought on testing than there are programmers. (At last count, at least three times as many.) Even if nobody is quite sure what role testing has, most seem fairly convinced it has got a role. One popular creed states that design should be from the top down and testing from the bottom up. (ie: test at the level of the components that call nothing else, then build up step by step.) Another states that since you have a specification (you do, don't you? :), you can write the tests according to the specification first, then write the code to comply with the tests. You can even follow both approaches, if it helps you feel better. Just pick something and stick to it. My preferred testing method is to check "typical" conditions, boundary (extreme) conditions and erronious conditions.
    • Never assume that some other coder's assumptions about the compiler's assumptions of what was assumed by someone else entirely bears any resemblance to what you think. Computers know all about luck and hope and how to utterly crush them when you're not looking.

    Yes, some of those do conflict. How to keep things simple AND have fault-tolerence, for example. That's where a good design comes in handy, because you can get a better feel for where you should make the trade-off between certainty of working, certainty of working later on and getting some sleep this side of 2008. It's all a matter of weighing the options and investing time in the place most likely to benefit.

    (Because everything is a trade-off, anything listed above may not apply. But then, it may not need to. If you've tested a component thoroughly along all boundaries, a good sample of valid conditions and a good sample of erronious conditions, AND everything has been kept as simple as possible so that really wierd cases are unlikely to crop up, then you may decide you can simplify or eliminate fault-tolerent components. There is no point in catching errors that won't occur. In fact, that adds complexity and violates the Keep It Simple rule.)

    Oh, and as this is a networked system, testing should include testing network I/O. Use packet generators if necessary, to see how the system handles erronious packets or massive packet floods. You don't want "perfect" responses (unless you can define what "perfect" means), you want reliable responses. If X occur

  • by wft_rtfa ( 882194 ) on Sunday February 05, 2006 @01:46AM (#14644591)
    Don't let anyone convince you that C# is going to provide more reliable execution.

    That is very true. However, if you're not a C++ expert or a C# expert then you will get a more reliable program much quicker in C# simply because it's easier.

  • Comment removed (Score:2, Insightful)

    by account_deleted ( 4530225 ) on Sunday February 05, 2006 @01:46AM (#14644594)
    Comment removed based on user account deletion
  • by Peter La Casse ( 3992 ) on Sunday February 05, 2006 @02:00AM (#14644643)
    I'm not an expert, but I too see lots of people missing the point, so I'll add my two cents. There are lots of comments talking about implementation techniques, and those are very good things to do, but that's implementation, not design.

    Design is about turning analysis models into implementation. Analysis models are the result of analyzing what your system needs to do; they describe what it does, not how it does it. Use some type of formal modeling language (there are several) to exhaustively describe the behavior of your system in a way that is provably consistent. Iteratively refine the formal model until you get enough detail to implement. Use tools (you might have to write some) to prove that your implementation matches the formal design model. When the design is good, use the techniques that others have mentioned to generate a rock solid implementation.

    Writing ultra-stable software is hard. Most choose not to do it. IMO it's less fun than coming up with something in your head and banging out some Python to do it, but it is possible.

  • by gadzook33 ( 740455 ) on Sunday February 05, 2006 @02:07AM (#14644664)
    You made a huge inline claim without really ANY backup!
    mmm...huge inline claim. Sounds like that could lead to memory thrashing. But seriously folks, if you took the time to actually read my post (you know, left to right, top to bottom), you'll see that I never made any such claim. There is simply nothing inherent in C# that will protect you from most classes of errors. Especially the really insidious ones that A.) You don't discover until it's too late and B.) Usually are the result of bad design, not bad language. At the end of the day, C#: Not that different from C++. The rules of logic still apply.
  • by logicnazi ( 169418 ) <gerdes@iMENCKENnvariant.org minus author> on Sunday February 05, 2006 @02:08AM (#14644667) Homepage
    Jesus christ people he is asking you how he should go about building an ultra-stable application in C++. He told you he *has* to build it in C++ because there are critical libraries and other components that aren't availible in C++. Telling him he shouldn't build it in C++ anyway just isn't helpfull.

    I hate to break it to people but there *are* libraries, especially for types of scientific computing, that are only (reasonably) availible in C++ or sometimes FORTRAN. Not only would abandoning these libraries mean he would completely have to reinvent the wheel but also might cause serious compatibility problems not to mention a much greater ongoing maintenence responsibility (he can't just check his program to make sure things still work when someone fixes a library bug).

    Moreover, the idea that because he is considering using CORBA, IPC or whatever else speed can't matter enough to require C/C++ is dead wrong. It is true that whatever *parts* of the process are done using these components may not require huge amounts of speed but this doesn't mean one of these components isn't doing something very processor heavy.

    In particular what he says sounds like the situation in some areas of scientific computing. If one is writing a program to do some sort of simulation or similar math intensive operations speed can be *very* important in the critical parts of the code but (in some cases) transfering information to the GUI or other components need not be particularly speedy (increasing by an order of magnitude may make a small difference in overall runtime). Imagine a program that does some kind of weather, or nuclear detonation simulation. The cross-processor communication and the core simulation kernel need to be very fast but the GUI and data input components need not be particularly fast. Also it is my understanding that often the critical libraries in this area are often only availible (at least freely) with C/C++ or fortran bindings.

    Anyway I think it is important to distingush several different goals, ultra-stability, minimal downtime, and minimal data/computation loss. For instance a climate simulation that may run on a supercomputer for months it is very important to have minimal data/computation loss (i.e. if something goes bad you don't lose months of very valuable supercomputer time) but you need not have ulta-stability or minimal downtime. As long as when any node crashes the simulation can easily be restarted without loss of data there is no problem. On the other hand if you are running a website like slashdot it is minimal downtime that is important it doesn't really matter if some of the web server processes are rebooted once in awhile. If, on the other hand, you are writing code to monitor a nuclear power plant it is ultra-stability that is important (though I can't at the moment think of something that requires distributed processing and ultra-stability but I'm probably just missing something).

    So I think the answer depends on what sort of stability you want. If it is important that no individual *node* crashes (though the GUI/other non-core components can crash) then you should pursue the seperation you described above. I have to admit I'm not an expert here but the client-server model (like mysql, X etc.) seems to work well in this context. However, this depends alot on what sort of data you need to transfer. If you just need to send the core setup commands and get back mostly unstructured info (say a grid of tempratures or other simple datasets) then I would suggest sticking with one of the simpler abstractions and don't get lost in CORBA. On the other hand if you need to send back and forth real objects with significant structure then creating your own serialization system/bindings is just asking for bugs.

    On the other hand if what you want is minimal data/computation loss, downtime, or any other property where it is the overall system you care about not a crash at any particular node then I suggest concentrating less on dividing any one node into comp
  • by SchroedingersCat ( 583063 ) on Sunday February 05, 2006 @02:10AM (#14644672)
    Here is the clue: if the code *relies* on being *managed* then the design is not stable. Well-designed system will not need a garbage collector, and poorly-designed system will not be saved by the garbage collector.
  • by DigitalCrackPipe ( 626884 ) on Sunday February 05, 2006 @02:13AM (#14644684)
    Avoid the latest "big thing" for the core of your project. It's usually specialized, non-portable, etc. The standard template library for C++ (for example) is here to stay, with tested algorithms that are safer and faster than you can usually write (because they are optimized for the platform you compile on). For the GUI, on the other hand, you may be better off with a GUI-based language/tool. That's less likely to be portable, but that's the way GUIs work.

    Next, spend some time upfront on your design, with things like use cases, sequence diagrams, and other visualization tools to help you understand just what you want to happen in best case situations as well as failures. The level of detail/formality required is a moving target, so update as needed. You should have a solid error detection/correction plan so that you can design each component to follow it. Also design for test and with logging - it will help you while debugging, while testing, and while fixing the bug the customer is seeing.

    Make sure management will allow sufficient time for testing. A lot more lip service goes into support for testing than actual schedule and money. Your test plan should be as bulletproof as your design.

    That's my 2 cents. And a random book recommendation: books like Scott Meyers' "Effective " provide info on effective/error reducing ways to use the language/libraries, but won't help you get started with the architecture.
  • by dezert_fox ( 740322 ) on Sunday February 05, 2006 @02:36AM (#14644735)
    He didn't complain about anything... you added that part. He asked for advice. Were more people to do so, instead of relying on their overconfidence (like you) to get jobs done, maybe we wouldn't have computer instability be such an issue.
  • by Nataku564 ( 668188 ) on Sunday February 05, 2006 @03:00AM (#14644788)
    Its not a tinfoil thing, its a stupid people thing.

    Microsoft doesn't want people to say "Interpreted", because that means slow. So they made up their own buzzword which means "better", or something. Its stupid, and all the MS Zealots have latched onto it. The VB.Net guy at work finally spouted it out enough times to annoy me into destroying his perceptions. Eventually it broke down into him getting frustrated and saying "Well what does interpreted really mean anyway, it could mean anything?", to which I said there was a very clear and distinct definition, and that he needs to read more books and less M$.

    Yeah, I know, hotspot compilation down to native code, Java guy rant, .NET guy rant, blah blah blah. In the end, its still interpreted - its just a very very smart optimizing interpreter.
  • Re:inline code (Score:5, Insightful)

    by countach ( 534280 ) on Sunday February 05, 2006 @03:16AM (#14644820)
    Perl? Fuck. He wants a stable app with good code. Sheesh.
  • by neutralstone ( 121350 ) on Sunday February 05, 2006 @03:44AM (#14644877)
    I really think you had better qualify this.  IMO, assertion failures do not *cause* problems; they are messengers, and the message is always this:  "Your program is broken."

    I don't think you want to *recover* from a broken state.  I think you want to debug it -- find out what went wrong, fix the code, recompile, test, and re-deploy.

    Because, if you get to the point where an assertion fails, it means the state of the program is corrupted, and therefore you can't trust any part of it; e.g., you can't trust error-recovery code to be well-behaved.  The best you can do is bring everything to a halt and fix the bug.

    There are rare exceptions (no pun intended) to this rule, but for the most part, if you write out a condition and say, "if this is false, then the program has a bug", then you have some explaining to do if you *don't* want to use an assertion.
  • by arivanov ( 12034 ) on Sunday February 05, 2006 @03:50AM (#14644890) Homepage
    Well... Someone modded this as funny. Wrong... It is the first comment so far I have seen on this article that comes anywhere near being insightfull.

    The secret of stable system design is designing from failure. Designing and implementing defensively. If you want to design an ultrastable system you start with the failure analysis for every component, following with failure analysis of modules and the entire thing as it grows.

    This in the world of C++ (and C for that matter) quite often means checking paranoiacally everything everywhere for NULLs before doing anything about it.

    Designing and writing from failure means that every system or library call should be assumed to fail first and all failures handled cleanly. This may be quite painfull because it usually requires the development of special tools like wrappers around malloc, file calls, etc that return error conditions which are nearly impossible to achieve on a live system.

    Only after all codepaths for "bad" results have been handled, the actual "normal" codepaths should be written. This unfortunately is not the way code is written in 99% of the shops out there. Most design and implement from success first and add failure handling later.

    Just ask in your shop: "Where is our memalloc wrapper that simulates a failed memory allocation? I need to link versus it to do some testing to see how our app handles NULLs in a few places". The usual answer you will get is "Ugh? WTF you are talking about Dude... We do not smoke that stuff here... Just go and write the code you have been assigned to write..."

    And the results are quite bloody obvious.

  • A few guidelines (Score:5, Insightful)

    by pornking ( 121374 ) on Sunday February 05, 2006 @03:51AM (#14644894)
    The suggestions I have seen here so far seem to boil down to "Don't do it that way". Sometimes that's not possible. If it truly has to be C++, and it truly has to be as fast as possible and as bug free as possible, there are a few guidelines that can help:

    1. Unless the GUI will be I/O bound, and that's unlikely, try to write it in a safer language that has better GUI support.

    2. Make all your classes small and simple, and create test harnesses that are as complete as possible. Try to make the classes simple enough that they can be individually tested in such a way that all code paths are exercised.

    3. Check your arguments. This includes checking for invalid combinations, and arguments that are invalid given the state of the object.

    4. Don't use new or pointers directly. If there may be multiple references to an object, then reference count it and create handle classes that hold the references so all instantiation is controlled, and all destruction is implicit. Make these handles STL compatible, and never pass around pointers to them.

    5. Try to design the application to fail fast and recover from failure. For example, maintain the state of work being done in discrete transactions that can be aborted if a failure is detected. This can be on disk or in memory depending on your performance needs. This could be combined with the ability to restart the app in a new process and have it pick up where the last one left off.

    6. Have the app keep track of its memory usage, and be prepared to recover from memory leaks, possibly by restarting as in item 5.

    7. If the compiler you're using supports structured exceptions, then use them. They can degrade performance a bit, but they can also enable you to recover from NULL pointer exceptions.

    8. If you have multiple threads, then to avoid both the performance hit from context switches and the chance of deadlocks, don't let them access the same data directly. Instead, have them communicate through lock free queue structures. That way, all your main threads can pretty much spin freely. Spawn worker threads for any I/O or other operations that can block. A context switch can take as much time as thousands of instructions. You want to use as much of every time slice as possible.

    9. Keep the number of main threads down to the number of CPU's or less. That way, except for the times when the CPU is being used by the OS or other processes, (should be relatively rare) each non blocked thread gets its own CPU.

    10. Have an experienced QA team, that understands their job goes beyond unit testing.

    Now here's a few that are always important, but for what you want to do, they become critical.

    11. Have the design laid out at least roughly before you start.

    12. If at all possible, don't let requirements change in midstream.

    13. Overestimate the time it will take very generously. You will probably still be crunched.
  • Recoverability (Score:4, Insightful)

    by marko123 ( 131635 ) on Sunday February 05, 2006 @04:58AM (#14645015) Homepage
    Rather than investing too much time and effort in creating a complicated crash-free program, just make sure your application can recover from a crash, and then use a process management application that restarts the program on it's node when it is detected to not be running properly.

    It's simple to write a 100% correct program that checks the health of your main application, and restart it when it isn't responding.
  • by CharonX ( 522492 ) on Sunday February 05, 2006 @05:01AM (#14645025) Journal
    I partially agree.
    If your code is unstable in a way that memory leaks and segmentation faults are not only a "remote possibility" but a - even if only rarely - reoccuring event, then any safeguards you implement won't be overly sucessfull, unless you fix the code that causes the errors first. (Disclaimer: There is no perfect code. Even if there were no bugs in the code, the program has still the "remote possibility" to crash due to errors in the hardware / OS)

    That said, garbage collection or not is a different discussion. Some say it is bad and breed lazy programmers, while others argue (I amongst them) that it is a terrific tool for designers, since it almost eliminates the occurance of memory leaks (unless you do some really bad programming) and it might even speed up your program [wikipedia.org]
  • by Anonymous Coward on Sunday February 05, 2006 @07:20AM (#14645262)
    I second that one.

    First rule of programming is You never disregard a return value.
    Second rule of programming is You never disregard a return value.

    If it can fail, you check and act on it. Event just to throw an error message and die. Usually, I undo all the things done before in the function. Then, when during testing someone comes back with an error message, the bug or problem (I deal with hardware, so bugs also occur in the FPGAs) can be understood in minutes, with no debugging session involved. For rarely occuring error that's precious. It's often advertised that catching a bug early save money, and you won't catch it earlier than by checking every goddam return value.
  • by hummassa ( 157160 ) on Sunday February 05, 2006 @08:05AM (#14645335) Homepage Journal
    of the thread... I'm appalled. I'll answer to this, when I would really like to answer to the main post, to maximize chances of you reading me.

    Question 1: what strategies should a developer take to insure that the resulting program is as crash-free as possible?

    Answer:

    a. Use OO techniques and maintain all objects in your system extremely simple; furthermore, maintain all methods in your system extremely short, well-contained, well-defined.

    b. Don't use C++ arrays, ever. Especially not for strings. Use and abuse the STL.
    copy( istream_iterator<int>( cin ), istream_iterator<int>(), back_inserter( v ) );
    is just plain beautiful IMH?O.

    c. Check extensively the behaviour of your constructors and destructors.

    d. Make a object-lifecycle diagram of each class you program. In the diagram, relate it to the neighboring classes (parents, children, siblings, classes involved in design patterns with, classes aggregated, classes value-aggregated, classes where this is aggregated or value-aggregated)

    e. Use, carefully, and always when possible, smart pointers. Remember std::auto_ptr is your best friend -- its limitations are a defining part of its strength. Remember boost::shared_ptr is also a good friend, but its cousin boost::intrusive_ptr is even more friendly -- but use one of those (and their other cousins scoped_{ptr,array}, shared_array, weak_ptr) only in the (rare) cases where auto_ptr does not apply.

    f. As a corollary to (e) above, use boost. This is really an extension of (b), too.

    Question 2: How can I actually implement such a decoupling?

    Answer:

    I would use a simple, socket-base, take-my-data, gimme-my-results scheme. It would be network-distributable, easy to detect if some service is or isn't alive via timeouts... If you want something more sofisticated/RMI-like, SOAP (with binary XML or compressed) may be an option. The simpler the better IMHO.

    Question 3: are there any software _design patterns_ that specifically tackle the stability issue?

    Answer:

    All of them? IMHO, DPs can represent huge tool to increase the stability of a system. Take a look athere [WARNING: PDF] [uiuc.edu] (and in the bibliography) for some ideas.

    I know many of my posts were self-marketing lately, but if you need someone to work with you, I'll be happy to send you my resume... write me at hmassa (at) gmail.
  • by Stocktonian ( 844758 ) on Sunday February 05, 2006 @08:06AM (#14645337) Homepage
    Wow, how did this get modded insightful?
    I'm the furthest thing from a Microsoft Zealot, I don't use any MS products and don't recommend them in my consultations, but I still know that Managed != Interpreted. You've mistaken the term "Just In Time compilation" (JIT) for "Managed Code". You even have the nerve to call other people idiots! Mono has an option for performing Ahead of Time (AOT) compiliation to produce native binaries that can still be considered managed because they have been through the verification process. I think the Microsoft equivalent is called Bartok.
    May I suggest you spend a little less time reading anti-MS propaganda and either read some of these books you are so keen to suggest or spend some time actually thinking about what you say, before you say it.
  • by chiph ( 523845 ) on Sunday February 05, 2006 @09:50AM (#14645530)
    Absolutely.
    What I'm hearing is the guy's boss telling him "And it'd better not crash!"

    Typically, when absolute reliability is needed (nuclear power plants, spacecraft, pacemakers), you start subtracting libraries which aren't known to be absolutely reliable, yet in this case they're adding them. In addition, he's wanting it to run on multiple platforms, which radically increases your testing workload.

    On top of that, he admits he's got no experience in the techniques needed to produce reliable software. Probably has a short deadline, too.

    My crystal ball says he's doomed to failure.

    Chip H.
  • by Lumpy ( 12016 ) on Sunday February 05, 2006 @10:18AM (#14645600) Homepage
    Jesus christ people he is asking you how he should go about building an ultra-stable application in C++. He told you he *has* to build it in C++ because there are critical libraries and other components that aren't availible in C++. Telling him he shouldn't build it in C++ anyway just isn't helpfull.

    And what he is asking can not be done BECAUSE of the libraries.

    you CAN NOT guarentee that the libraries are 100% stable. Typically I find that stability points to the libraries and you have to write your own to get your high stability.

    Granted this is from an embedded systems point of view. but if he is looking for ultra stable (5 nines) then he has to change things. Depending on XYZ library means that your stability can only be as stable as THAT library and some of them out there really suck at stability.
  • by defile ( 1059 ) on Sunday February 05, 2006 @10:38AM (#14645658) Homepage Journal

    In a desperate rush for some reading material for the toilet, I grabbed what must be a 5 year old C/C++ User's Journal from a storage room. The theme of that month's issue was MULTITHREADING.

    I thumbed through it and came across an interesting article ``ALWAYS HANDLED ERROR CODES''. The idea being that a lot of errors can go undetected because programmers are lazy about checking return values. And why not, who bothers checking printf()'s return value, for instance?

    Simple enough design. The object constructor sets the result, the destructor will abort() the application if the Checked variable is false. The overridden == and != operators evaluate the result, and also set the Checked variable.

    In your functions, instead of return SUCCESS; you write return ErrorCode(SUCCESS);

    Wondering if anybody does this. If I needed something ULTRA STABLE I guess I might...

  • Re:Obvious ! (Score:3, Insightful)

    by GroovBird ( 209391 ) * on Sunday February 05, 2006 @11:07AM (#14645743) Homepage Journal
    Well since the parent is posting a link to a satirical website I wouldn't moderate his post as "informative" but oh well.
  • Re:inline code (Score:2, Insightful)

    by OOGG_THE_CAVEMAN ( 609069 ) on Sunday February 05, 2006 @11:23AM (#14645817)
    The failure mode of circular references defeating reference counting is more general than the toy example.

    It can very easily occur as one builds up complex data structures to make more abstract representations possible, and then using the abstractions in straightforward ways. Imagine embedding a table in an object, used to efficiently find neighboring objects in a network. If that network has any cycle in it, boom. What do you do if the user is the one building the network? Tell them not to use your tool for networks with cycles?

    This is analogous to the problems of manual memory management. In simple cases, i.e., simple enough to see the problem, you can always come up with a "simple" solution: i.e. "caller owns the resulting allocated object, and is responsible for deallocating." The problems develop when the call and ownership chains become complex, and the protocols built on top of protocols eventually fail to cover edge cases. Then, you've got a hell of a mess to crawl through to plug the leak.
  • by canuck57 ( 662392 ) on Sunday February 05, 2006 @11:40AM (#14645875)

    Follow NASA's advice... http://www.fastcompany.com/online/06/writestuff.ht [fastcompany.com] ml [fastcompany.com]

    Your post should have been ranked informative +10 and is underrated. Those that think they are "professional" programmers aught to read this and memorize it. This thread has so much BS about what is right for making code stable it just shows how many poorly qualified people there are out there. But for other readers---

    I have been in both kind of shops, dime a dozen out of control cowboy mentality workshops, and a few others with high standards in planing, process, controls and testing procedures. The later produces better software that is not only less flawed and runs better, but costs less in the long term as you don't need an army to program it or to support it's quirks.

    Lets dispell some myths:

    • At minimum, one hour of rational planning and engineering with save 100 hours of programming and 10000 hours of support.
    • Bad code is bad code, it does not mater if it is C, C++, Java, Python, C#, J# or whatever. Adding another langauge will add complexity but solve nothing. If you want to fix it you need a real software engineer and not a programmer.
    • 9 of 10 programmers are incompetant, at best. If they were compared to brick layers, 9 of 10 could not make a straight or vertical wall. Same goes for software managers. This is the real reason of outsourcing.
    • Those that do know how to code either work alone or in qualified shops with diciplined processses. The later being the best.
    • Most I/T shops have the incorrect attitude, "we are not going to kill people by rushing this, so quicly do it". The truth is they don't know how and are incapable of doing it right the first time and have come to accept the high maintenance costs associated with sloppy computing practices. As their user perception deteriorates, another reason to outsource.
    • Ever notice how I/T vendors bypass techs? Don't trust those who do. If you have a wizard on staff that is socialble, use them but don't abuse them or they will leave. If they are anti-social, loos them.
    • Management is shallow in their vision, look at the long term in team development and raising the bar of the quality of software products. There will be less to worry about when you sleep.
    • The act of making the programming changes should be mechanical, repeatable and quick if planning and engineering are functioning correctly. If your programmers need spend more than 8 hours overtime per month your engineering and planning needs development. Also, if you have more programmers than engineers your resource base is out of whack.
    • Want to manage your project for success? Work the process not the technology.
  • by j.leidner ( 642936 ) <leidnerNO@SPAMacm.org> on Sunday February 05, 2006 @12:07PM (#14645966) Homepage Journal
    I agree. Haskell is a bad choice _at_present_ at least for the following reasons: (a) hard to find qualified staff to maintain the code, (b) language not standardized and still in flux.

    This is not a comment against Haskell, but against suggesting it as appropriate means, given the poster's situation.
  • by Anonymous Coward on Sunday February 05, 2006 @12:15PM (#14645991)
    You have it correct. I worked in the GN&C FSW team this article describes from 1989 to 1994. I wrote code under an ever changing and improving process with a team of excellent managers, requirements analysts, peer developers, and Level1/2 testers. Generally, we worked 8 hours a day together. Overtime **was** the exception. Generally, our families got together outside work at least one night a week for softball, basketball, vollyball, or "off site team building" .

    In the real world - I work in telecom now - the idea of what a "bug" is, is completely different than what the GN&C guys thought a bug was.

    I've worked in places where everyone is fanatical about quality and even a memory leak found in your code what considered a failure to a place where accelerator keys where never tested because the gui coder and tester only used mice. The accel keys worked as a fluke, not as a rule. That code crashed constantly, except on the GUI programmers' machine. He always said, "it works on my machine." Like that is useful to a customer.

    High quality and productivity standards, repeatable processes and schedules, and "being careful what you measure, because that is what you'll get" is what I learned from my GN&C experience. At the time, I didn't realize it, but that was probably the best job I've ever had. Except for the paycheck, that is.
  • by mkcmkc ( 197982 ) on Sunday February 05, 2006 @12:17PM (#14645999)
    [Y]ou are assuming that a language like Python translates line-by-line into C++. Does it?

    I've been following this methodology (Python first, then C++ as/where needed) for a number of years. In all of that time, I've only had one application where I ended up needing to drop into C++ at all. In that case, a couple of pages of Python did translate into a couple of pages of C++, virtually line for line. Heavy use of STL allowed this, as there are a lot of data structures and algorithms there that map more-or-less directly to Python. The main problem was that the STL tended to be either buggy or to have razor-sharp edges upon which to cut oneself.

    Python is generally a win because (1) you can write the same working functionality much faster than in C++, and (2) the specifications for apps tend to vary wildly over time, so a high-level language lets you go with the flow.

  • by Chemisor ( 97276 ) on Sunday February 05, 2006 @02:10PM (#14646444)
    > copy (istream_iterator(cin), istream_iterator(), back_inserter(v));
    >
    > is just plain beautiful IMH?O.

    I'm sorry, but I just can't agree. It might appeal to a mathematician who wants to see everything use functional notation and hates every language except lisp, but to a non-abstract-elite-ivory-tower-mathematician this is absurd. cin is not an array of integers and the use of the adapter obfuscates the fact that you are using a conversion from a char array to an int. The back_inserter also makes it harder to see where the data is going by losing "v" in it. Many would also frown at it for taking a non-const reference, although since it is a standard adaptor it is probably ok.

    C++ programmers are often unnaturally attached to efficiency and have to be watchful for template bloat. Your copy generates 88 instructions, whereas an equivalent iterative solution is only 33 instructions long, most of them belonging to the inlined push_back. Not only is the generated machine code smaller, but the source code is smaller as well, and is far more readable, making the algorithm obvious at a glance to any procedural programmer, who make up the majority outside the hopelessly out-of-touch with reality academia.

    int n;
    while (cin >> n)
    v.push_back (n);

    Academics love integer and float arrays because that's what they usually work with. Scientific simulations produce data in that form and require processing programs that take something from a data file, crunch some numbers, and output something to cout. In the real world people work on user interfaces, databases, and other complicated things, where one normally works with arrays of objects rather than numbers. If you ever tried to apply a functional algorithm to a vector of objects, trying to manipulate some member variables or call a member function, you would know that the result is so hideous that it isn't even worth considering. There is a reason people prefer iterative solutions; they are how the real world works. Reality is algorithmic, not functional, and so are user specifications for the things they want done. Trying to cram them into an abstract mathematical functional model is insanity.

    > Use, carefully, and always when possible, smart pointers.
    > Remember std::auto_ptr is your best friend

    Most of the time, no. While I would not deny the utility of auto_ptr in localized situations manipulating the object state during reallocation, its constant use indicates lack of understanding of object lifecycle in the program. It is fashionable in Java to create objects left and right, without consideration of who is supposed to own them. Hey, just let the garbage collector take care of it! Who cares how long the object lives? Obviously, such immature mentality produces plenty of memory leaks for which Java is so infamous. In a good design object ownership is strictly defined. Objects belong to collections that manage their lifecycle. There ought to be no "dangling" objects that just "hang there". If you don't know to which collection the object belongs, you have no business creating it. If you think your objects are "special", you haven't thought beyond their internal functionality or considered where it fits in your overall design.

    > Question 2: How can I actually implement such a decoupling?
    > I would use a simple, socket-base, take-my-data, gimme-my-results scheme

    And thereby slowing your program to a crawl? There is a reason people use CORBA and the like: those frameworks optimize distributed object calls to avoid network hits, often being able to reduce the overhead to be equivalent to a virtual function call. Furthermore, networked applications have their own set of complexities and security considerations. You get to keep an open port somewhere, handle authentication (becase wherever there's an open port, there will be malicious connections), and extensive data validation (for the same reason). While these problems are applicable to dis

  • Re:inline code (Score:1, Insightful)

    by Anonymous Coward on Sunday February 05, 2006 @03:24PM (#14646711)
    Just because you can't read or write Perl proficiently doesn't mean that's a problem with Perl... check out Perl Best Practices...

    But one could make the same argument about C or C++, or even Visual Basic or assembly for that matter. An experienced coder applying discipline and good habits to a design problem is far more likely to produce a quality system than a newbie or a weekend hacker.

    Some languages seem to encourage good habits more than others. I know very little about Perl, but it's reputation in this area is infamous. "There's more than one way to do it" is pretty much the opposite of encouraging good habits. Just because it's possible to write stable software with a particular language doesn't mean that it's a great choice when stability is very important.

    My own feeling is that when stability is important, one should 1) use a familiar language, 2) use the best habits/practices, and most importantly 3) be willing to spend lots of time specifying and designing before writing even one line of code. Ideally, the specs should be such that one could turn them over to an outsider, and he/she could produce a working system from them. This is the way things are done in mission-critical embedded apps, and it does make a difference. Productivity undeniably suffers, but the benefits in code quality become very obvious.

  • by Anonymous Coward on Sunday February 05, 2006 @03:29PM (#14646728)
    You are confusing interpreter [wikipedia.org] with virtual machine [wikipedia.org]. You can have a compiled binary for a virtual machine that is not a native binary. You can also have an interpreter for a virtual machine (e.g. the original JVM), or you can have a JIT compiler for a virtual machine (e.g. the CLR). The former executes the virtual machine binary, the latter creates a native executable from the VM binary.

    Microsoft uses the term "managed code" to mean a CLR binary that has a certain set of guaranteed properties: verifiable type safety which means no illegal memory accesses, and enables strict garbage collection, and fine-grained security control. You can write unmanaged code for the CLR (i.e. create VM binaries, that are not type safe) that still follow the same execution path (compiled to a CLR binary and then JIT compiled to be executed).
  • by Chemisor ( 97276 ) on Sunday February 05, 2006 @07:15PM (#14647415)
    > Not understanding something is one thing, but not understanding something
    > so let's reject it as being "elite-ivory-tower"

    I did not say I did not understand it. I said I did not like it. I do not like it because it does not fit with the reality of computer operation, as discussed below.

    > Reality is reality. Algorithmic or Functional are just ways people look at it.

    On the contrary, you can see reality being algorithmic. Things happen one after another. To type "algorithmic", you depress a, l, g, etc. in order; you don't declare a set of letters, fill it with appropriate values and throw it at the computer. When you receive a specification for your program, it will say something like "get this from the user, then do this, then do that, then print out the result". No specification is ever written in functional notation outside the academic world.

    More importantly, the computer itself works algorithmically. It does one thing, then another. No computer has ever worked functionally, and no computer ever will. All of them will decode and execute a sequence of instructions, and if you refuse to write your code likewise, you're only adding translation overhead.

    Even in the hallowed halls of science overuse of the functional notation creates serious problems. The entire hodge-podge nonsense we call quantum mechanics stems from the attempt to describe a complicated system as a function. Instead of trying to get a set of time-value maps for the whole system, it would be more appropriate to look at the system's constituent parts and algorithmically simulate them through time. That way you wouldn't get any "spooky action at a distance", stuff being there and not there at the same time, and all other equally ridiculous denials of reality.

    > I advise you, that your use of the common peoples' fear for mathematics
    > in your arguments is not going to help.

    I wasn't using that argument, but, now that you mention it, it is a reasonable one. Most programmers couldn't care less about higher mathematics, and, even if they were forced to study it in college, they likely have forgotten it all by now. Computer algorithms require minimal mathematical background. The most I ever used was a bit of calculus to write scan-conversion routines. So, whether from lack of practice, or from lack of interest, most programmers will prefer you didn't drag them into the world of useless mathematics. (and I use the word literally here)

    > Templates, being code generators, differ by nature to hand-tuned codes.
    > So your code generates only 33 instructions vs the template's 88. Great
    > - now tell me - which architecture? What compiler?

    That is quite irrelevant in this case. istream_iterator notation generates extra code for reasons that will not go away no matter how hard you try to optimize it. Yes, I might be able to write an istream_iterator that would have no overhead over my iterative version, but it will not be standard compliant. The istream iterator has to read on construction; it has to store the read value; it has to be constructed, since it must keep a reference to the source stream; it has to handle special cases, like the end-of-file, and the subsequent conversion to the end iterator value. However good you might be at optimization, you will not be able to discard these and still be compliant with the specification.

    Also, which compiler or architecture you use will not make all that much difference in the size of the compiled code. I guarantee you that your functional copy will never generate smaller code than my iterative loop, no matter what compiler you use or what architecture you compiler for. There is a certain amount of work to be done, and my version does less work. It is as simple as that.

    > And before you count the instructions, did you realize that this code is waiting
    > for keyboard inputs, therefore what you're doing is unnecessary (and obviously
    > premature) optimization?

    First, you should note that I

With your bare hands?!?

Working...