13

There is a problem with the way I code. Regardless of how much of a plan I write beforehand, the code becomes overcomplicated quickly.

Reading books on good practice and attempting to adhere to their principles isn't working.

Is there some surefire/proven way of auditing code to simplify it, other than being more thorough and dedicated in the planning stage?

6
  • 9
    Related reading: Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin.
    – user22815
    Feb 9, 2015 at 20:56
  • Thank you, @Snowman. I'll make sure I pick up a copy and keep it handy to avoid further duff-ups Feb 9, 2015 at 21:34
  • 28
    Planning beforehand will not prevent anyones code to become overcomplicated. Refactoring does - immediate refactoring - multiple times a day. For example: whenever you have made a function a little bit more complicated by adding something, think about splitting it up into two or more smaller functions. Whenever you see a group of parameters passed around between a handful of function, group them together to a datastructure and give that a meaningful name. Whenever you see a variable and you wonder what it is good for, give it a better name. And so on.
    – Doc Brown
    Feb 9, 2015 at 21:35
  • You are supposed to follow advices from experts in this area. They often compile books containing Do and Donts according to their experience and in some case numbers from real projects. I would suggest asking for "which books which help ..". A question like this would often be an entire a field where books can be written with many pages.
    – InformedA
    Feb 10, 2015 at 9:16
  • 2
    I'd also recommend Code Complete as a good resource.
    – David
    Feb 10, 2015 at 12:59

8 Answers 8

20

There are indeed techniques for gradually simplifying code and improving design by doing so. The general technique is known as "refactoring". Note that this term is used both informally (to mean "anything that changes how code is designed without changing what it does") and formally (to mean "following a specific refactoring discipline"). I am referring to the latter definition here.

In particular, this problem has been much studied by Martin Fowler, and his book "Refactoring: Improving the design of existing code" is one of the classic books of software engineering, and is well worth reading in this area.

The general technique that the book suggests is:

1) Write very detailed tests for the section of code you intend to refactor.

2) Make a specific SMALL change to the code. For example, inline a method, rename a class, extract some code into a new method, extract a superclass from an existing class, etc. (Fowler has detailed lists of many such small transformations in his book, along with suggestions on how/when to do them. Most of the names of existing "automatic refactoring" features such as those in Eclipse, IntelliJ, etc. were taken from those used in his book. Essentially, these are like "design patterns" but are used for modifying existing code rather than for designing it in the first place.)

3) Re-run your tests, to verify that you haven't broken anything yet.

4) Repeat until you are satisfied with the results.

Thus, large changes to the code can be made by accumulating multiple small changes, with testing between each change.

Obviously, if you are in the habit of writing and retaining detailed tests for your code in the first place (a unit test suite, regression test suite, etc.), the above process will be much easier than if you have to write the tests immediately before you start refactoring. Also, it will be much easier to do this if the part of your test suite that you are re-running between each change is capable of running quite quickly.

Note that refactoring code in this way is a DISCIPLINE. It is not the same as simply randomly going around changing code. The frequent testing during the process is essential for ensuring that any inadvertent changes that you make to the program's behavior are quickly caught, while you can still remember what you did. For this reason, it is also often a good idea to do refactoring separately from other development which adds or changes the behavior of the program, since those sorts of changes are likely to invalidate existing tests. You could, for instance, alternate phases: Add a feature, then refactor a bit, then add another feature, then refactor some more, then fix a bug, then refactor, etc.

If you are consistent in programming using these techniques to simplify your code, the overall trend of your code over time will be that it gradually becomes simpler and easier to read, rather than the reverse (which is, sadly, more common in industry).

You may also find that some of the community around the Agile and/or XP software development methodologies may be helpful as to how/when/what to refactor, since continuous refactoring in this manner is considered a required feature of many such methodologies.

I have seen many projects gradually become so crufty and complicated that nobody understood them and they eventually had to be discarded, because maintaining them eventually took more effort than rewriting them would. Refactoring is a way to avoid this problem.

4
  • 5
    While I agree with most of what you say, there is one flaw I would just like to point out: "Write very detailed tests for the section of code you intend to refactor" - This implies that the code is testable at all. Too often have I encountered sections of code which are so dependant on external resources that make then untestable. So, if you cannot do that, you cannot really rely on the tests. Just saying...
    – F.P
    Feb 10, 2015 at 7:40
  • 6
    @FlorianPeschka when handed code like what you describe to refactor, the first refactoring step is to make it testable and so limit the risks of further refactoring
    – sq33G
    Feb 10, 2015 at 9:38
  • 1
    I am aware, just wanted to put it out there to make people think of it :-)
    – F.P
    Feb 10, 2015 at 10:56
  • And I would add "Utilize versioning system like git to keep track of changes" (that way you can always go back easily).
    – rluks
    Jan 14, 2016 at 19:38
47

Practice.

No, really.

You will never fully appreciate a technique in a book or blog post until you've written your first plate of spaghetti or big ball of mud, and realize that many of these techniques are just effective ways to tame that complexity. Then you will understand them at a deeper level, much deeper than the cargo cultist who is just stringing patterns together to look cool, but doesn't fully understand the reasons behind them.

Check out the patterns. Are any of them easy? Well, they're definitely easier than writing a plate of spaghetti or a big ball of mud, and having to maintain it later. But you still have to make a study of these patterns. Even the best painter uses more than one brush.

6
  • 7
    +1 for practice. It's the only way to get better at anything. There's no shortcuts.
    – Telastyn
    Feb 9, 2015 at 21:12
  • I've looked (briefly) at patterns. I understand that they are good because they provide structure and better minds endorse them, but they appear to be scaffolding and bracing that doesn't obviously contribute to getting code written. They confuse me (or maybe it's the superficial look at them). Feb 9, 2015 at 22:01
  • 4
    @AgiHammerthief: my personal impression of patterns, and this may not be universal, is that they're completely meaningless until you've hit the problem that the pattern is intended to solve. Then if you're really lucky you remember the pattern and find an effective way to apply it to your version of the problem. More likely you screw it up, come up with a half-assed almost solution, maybe a few times, and then you figure out a way to make the pattern help you and you solve such problems more simply and neatly in future. Or, you know, some patterns are just useless ;-) Feb 9, 2015 at 23:49
  • 7
    @AgiHammerthief: Some people think that design patterns are things you should do, as if they were a checklist of some kind. Not so. Design patterns are a language you use to communicate to other programmers the ideas you're using to solve your problems. That is, when someone tells me they're using a Factory, or a Builder, or whatever, that's just a way to communicate. Patterns don't themselves make code good or bad -- the code can be that all by itself. Feb 10, 2015 at 1:41
  • 1
    Also make sure you can identify when a certain pattern is useful and when it's not. Trying to mould some code to fit a design pattern when it's not appropriate can overcomplicate code just as badly.
    – Roy
    Feb 10, 2015 at 13:26
23

My one surefire way is:

Not being afraid to refactor the plan at the first hint that the code is turning complex.

(Which, of course, does not negate at all what you have already mentioned about being more thorough and dedicated in the planning stage.)

2
  • 12
    I would add that having good tests is often crucial to not being afraid of refactoring code. Feb 10, 2015 at 1:47
  • 3
    And the process of making code testable will often impel you to refactor it. One of the marks of well-structured code is that it is easy to test. Feb 10, 2015 at 10:42
12

Post your code to Code Review, and let someone else simplify it for you. ☺

In seriousness, if you study the simplifications recommended among the thousands of Code Review answers, you'll find that the range of techniques is far too broad to summarize in an answer. Just off the top of my head, I can think of:

  • Don't reinvent the wheel. Don't write code at all, if possible.
  • Pick the right programming language for the job. (Joining two datasets is vastly easier in SQL than in Java, for example.)
  • Write code that is idiomatic for the chosen language. (For example, in Python, take advantage of itertools and list comprehensions.)
  • Use an appropriate framework or library where possible.
  • Pick the right algorithm. (Sometimes there will be a speed / simplicity trade-off, though.)
  • Avoid premature optimization.
  • KISS and YAGNI
  • Pick the right data structure.
  • Use recursion / iteration where appropriate. Use OOP / functional programming where appropriate.
  • Use design patterns that fit your problem.
  • Refactor relentlessly.
  • … and more…

Basically, as @RobertHarvey says, Practice. But now I've given you a resource where you can take advantage of other programmers' experience.

2
  • It's important to separate YAGNI from what I'd call "NIGNI". The idea behind YAGNI is that one shouldn't do something now to fill a possible future need if one could just as well do it when the need actually arrises. NIGNI ("no one is going to need it") is what one effectively says [or hopes] if one declines to include something which may later be needed but which cannot be done later. NIGNI comes up when deciding on the public and protected members a library should expose; the fact that an author might find the library's own API adequate doesn't mean that consumers won't have...
    – supercat
    Feb 10, 2015 at 21:09
  • ...other needs which could be satisfied if the library's internals were available and documented, and cannot be efficiently satisfied otherwise. Keeping the internals hidden retains for the library author the ability to change the information later, but at the expense of possibly limiting what consumers of the class will be able to do with it. The class author isn't just deciding for himself that certain things won't be needing--he's deciding for everyone else that nobody is going to need it (or if they do need it, they won't get it).
    – supercat
    Feb 10, 2015 at 21:12
9

The easiest way is to get yourself a mentor who writes clean code, show him or her your messy code, and ask what's wrong with it.

The thing to realize is clean code doesn't happen at the planning stage, and you can't continually "adhere" to clean coding principles. You can only periodically reset to them. You will write messy code. As soon as you recognize it, you fix it. The more frequently you reset your code, the better it will be.

You said you've read books on the principles, so you know what they are. You can be methodical about resetting to them. It's often helpful to take a principle to extremes before backing off. Like tuning an instrument, it's easier to hear the proper set point if you first go way past it. If your function is way too long, split it into the smallest possible pieces, then recombine a little perhaps.

6

....no. There is not.

I never really thought before of the idea of such a thing existing because it's relatively obvious there's no perfect solution, but now that you mention it....that would be awesome!

However no, there is not nor will there ever be a perfect solution to ensuring your code isn't terrible, quickly, slowly, or inbetween (sluickly?).

2
  • I'm not naive enough to believe there is a perfect or faultless approach. I'm looking more for a way of how to be more self-aware and better equipped to avoid/correct bad tendencies. Feb 9, 2015 at 21:39
  • 1
    @AgiHammerthief you asked for a surefire/proven way to simplify code. There are no sure things, no perfect guaranteed way to simplify code. If there was you'd just apply it ad nauseum and all code would be reduced to it's most concise form.... Clearly there can be no such thing. Feb 9, 2015 at 22:00
3

If at all possible, use an IDE supplied with (at least) these two components:

  • some kind of static code checker with metrics and, if you can, a report tool.
  • a quick way to refactor code (hunt symbols and so on).

Books are great to understand the why and the how - you need a tool that will pitilessly ferret out the what and the how much.

You can then check periodically the code you wrote, and wherever e.g. the cyclomatic complexity of a function exceeds a predetermined threshold, you refactor the function. And so on. Put aside some time in each development cycle and devote it to refactoring; that's a possible "when". With time and practice you'll be able both to better estimate the refactoring overhead, and reduce said overhead by fixing most things almost automatically as you go by. Also, you'll be able to see what metrics (and values thereof) work best for you.

(Finally, "overhead" is a mistaken label - think of it as "small and comfortable down payments that allow your code not to go into huge technical debt, and bankrupt the project at some time in the future").

3

In my experience, if a software developer doesn't properly understand the system/domain he is writing the code for then the code is quite likely to become over-complicated.

The reason for this is fairly obvious. Simple code has a structure that mirrors the problem being solved or the domain being modelled. That way the code's behaviour tends to emerge from the structure, rather than being encoded as a large number of explicit cases.

Another way to spot this error is when you need a large number of test cases to check that the code is working properly and you discover failures in all sorts of edge cases. If the software had been designed well then there wouldn't be as many edge cases.

So work hard at trying to understand the problem domain or the system you are modelling. Don't just accept a specification or set of requirements from someone else and code to that. Break down barriers between the domain experts and the software engineers; become a domain expert yourself.

Not the answer you're looking for? Browse other questions tagged or ask your own question.