So, I actually have around 24 years in IT now. I usually cut down on this like some chump not wanting to spook their much younger date. Seen a few changes, lots of new whatevers that are going to change the world blaah. You have your own list of these. But we never seem to address the key issues: most people writing software don’t know how to. They’re techies that luxuriate in a complex ‘if’ statement, that fantasize about shifting a few bits in a gloriously complex nested statement that also predicts the day of the heat death of the universe. What they write ain’t simple. So there are plenty of pages here that discuss the ways that code should actually be written but AFAICT not a one page, executive summary. So can I propose this. Five Magic Rules that if followed will automagically make your code sweet. That, without using an IOC container, will make your code more flexible and attractive to countless high flyers with long tanned legs. Here we go: * '''SDC:''' Self-documenting code. ** Write code for reading. ** Use meaningful names. ** Create methods, ditch the long complex '''if''' statement for a method that describes what you are testing. *** ''This is disputable as an absolute. Too many methods/functions creates its own kind of clutter, bloat, red-tape, and maintenance taxes.'' ** Do not excessively nest calls. ** '''Separate policy from implementation.''' ** Keeping what separated from how makes it more readable and maintainable. Ensure those methods are the right ones. *** ''Separating policy from implementation is, in the general sense, extremely non-trivial. Related: CrossCuttingConcerns.'' ** Don’t write this: reader.readCurrentAccount(); ** write this: currentAccountReader.read(); ** Follow this rule and you can read your code rather than be forced to translate it. * '''ORR:''' OneResponsibilityRule. ** A class or method performs one job. If it doesn’t, break it up. ** You’ll lower your complexity, you’ll be able to unit test more effectively, you’ll be able to reuse more. * '''HCR:''' High cohesion rule (CouplingAndCohesion). ** Put the code on the right class. ** Don’t be tempted to write the code where it’s used, in a service on another domain model object. ** Take the time to move it to the right class. ** You’ll improve your reuse, you’ll be more able to swap behaviours when that new requirement comes along 2/3 the way thru the project. * '''LCR:''' Low coupling rule. (once more CouplingAndCohesion) ** Everyone know about decoupling tiers, but this must apply all classes you write also. ** Don’t have one class dig into another, send it a message, get it to answer with what you need. (Ah, sending a message doesn’t mean building something and chucking it on a queue, it means call a method on the object. (Wish I was still using Smalltalk.)) ** Don’t write this: if task.getStatus().equals(TaskState.COMPLETED_STATE)) { ** write this: if task.isCompleted() { ** Behavioural methods are the key to writing self-documenting code. * '''TIR:''' Test It Rule ** UnitTest everything that moves. So. I contend that these rules are all you need. Have I missed one. Or does everyone think that actually DSL’s make all this really rather academic. They _will_ save the world. Though personally, I’m not sure what planet that is.... -- Chris Brooks ''Chris - I observe that with the wisdom you possess and are kind enough to share, you should find it easy to learn how to format things in this wiki. I ask that you do so, so your advice is easier to read, and easier to concentrate on. I've no doubt kacked your advice about and may have shifted the emphasis inappropriately. I apologize if that's the case, but I found the original unreadable.'' ---- DomainSpecificLanguage''''''s by themselves won't "save the world"; their use and design is subject to the very same rules as other common programming constructs - especially APIs, framework, and library design. Many of your rules certainly apply. But EmbeddedDomainSpecificLanguage''''''s have real potential to reduce programming complexity (AccidentalComplexity). It is well observed that no single paradigm seems to work well in all programming cases, especially if the goal is to '''separate policy from implementation'''. Sometimes you need reactive programming, sometimes workflow programming, sometimes imperative, sometimes logical or relational, sometimes service oriented, and sometimes a combination of all the above. Access to powerful metaprogramming facilities to create (and interleave) task-related languages can make the efforts of Greenspunning a solution more natural and much more efficient (via partial evaluations and sharing of the optimizer). You mention several rules above related to testing, CouplingAndCohesion, etc. Other rules to pay attention to are OnceAndOnlyOnce, DoTheSimplestThingThatCouldPossiblyWork, YouAintGonnaNeedIt, RefactorMercilessly, RulesOfOptimization, and PickTheRightToolForTheJob. All this 'one class one task' stuff often starts breaking down when faced with common issues of logging, optimizations, and concurrency management. I suggest you wikify your above advice. Links to CouplingAndCohesion, UnitTest, etc. are appropriate. ----- Hmm, the DSL comment was intended flippantly, as was most of the advice that appears to have offended some (or maybe one I haven’t been tracing the changes) Point I was endeavouring to make is you don’t need DSL’s to manage complexity you do that by writing simple code. You mention valuable stuff, though some isn’t related to code structure. You’ve either succumbed to YAGNI or you haven’t. Doesn’t so much affect the structural simplicity of what you have written. I’d be interested in some examples as you why the OneResponsibilityRule doesn’t work, seems to work for me. (Ooer, does that sound smug?) Also as a rule is the separation of policy and implementation to be scorned? Or followed unless doing so is not the simplest thing the could possibly work - * OneResponsibilityRule usually fails when it comes to CrossCuttingConcern''''''s, a variety of which (logging, optimizations, concurrency, supporting reactive programming, etc.) are mentioned above. The ''principle'' of OneResponsibilityRule is itself well adapted to CouplingAndCohesion, and I have no objection to it. But assuming the OneResponsibilityRule will work in all circumstances would require a language that accomplishes all possible KeyLanguageFeature''''''s - an impossible task. Oh but hang on, if I DoTheSimplestThingThatCouldPossiblyWork, then that would be to inline the code in the calling routine here – as that means I don’t have to go across to this other class and write the method there. (one of the dictionary definitions of "simple" is "easily done") * DoTheSimplestThingThatCouldPossiblyWork does, indeed, refer to "easily done". By itself, DTSTTCPW would be a bad policy resulting in a massively hacked-together mess of code. That is why this principle is tempered by use of RefactorMercilessly to achieve OnceAndOnlyOnce. You don't need to "go across to this other class and write the method there" until ThreeStrikesAndYouRefactor. ** But there I would disagree, the code needs to be moved before you start to write it, because you should spot the code smell: it's likely to fail the rule of High Cohesion, and SelfDocumentingCode , as well as the appropriate separation of policy v implementation rule. So here's a (slightly altered example) from some code I looked at recently. Developer wrote: if((model.getControlDefinition() !=null ) && (model.getControlDefinition().getTaskDefn() !=null)) instead of going across to the model class and creating a new method, ie if (model.hasTaskDefinition()) which is what he should have done before he started typing. IMHO anyway... ---- Anyway, excuse me while I go and reformat some unreadable code, opening braces demand a new line in my bible! ----- You have 2 rules listed that, though both good, are hard for inexperienced programmers to apply simultaneously. * Create methods, ditch the long complex '''if''' statement for a method that describes what you are testing. * Do not excessively nest calls. It is therefore not enough to list them, one must give some examples of techniques for achieving both of them at the same time. After all, how does one decompose a complex nested structure be it loops, conditionals, or whatever if one does not factor out functions which become deeply nested calls? In the case of nested loops, I like to recommend collection-based programming for smallish loops or filters for very long ones. To unstack complex if statements, one can replace conditional logic with method dispatch, store intermediate Boolean results in intermediate variables, or allow condition clauses to be repeated in multiple branching clauses. ----- See also: TwentyFiveOrSoRulesToBuildSoftwareThatWorksAndWhichIsEasyToMaintain ---- CategoryMethodology, OctoberZeroEight