12

--EDIT--

I believe this is a valid question that may have multiple answers (as defined here). This is NOT a discussion nor a poll. Furthermore, this question is evidently NOT argumentative as none of the respondents so far seem to argue with each other.

--ORIGINAL TEXT--

Amazing! I do software for about 15 years now and I still have no idea what I'm doing :)

Seriously, I still struggle with the basics:

  • overengineering vs. YAGNI
  • cutting corners in order to meet the deadline vs. pushing back on the business
  • risky innovations vs. tedious good old stuff
  • enjoying the freedom of working alone vs. the power of the team

But the worst of all is code complexity. My code tends to get evil. It even bites myself after a few weeks. I put tremendous effort in keeping it simple, readable, maintainable, elegant, beautiful, cohesive, loosely coupled, based on nice straightforward abstractions. But all this effort goes down the drain!

Don't get me wrong, my code is pretty good by most people's standards. I mean it's flexible, more or less unit-testable, and does what it needs to do. However, it is far from simple.

Every change requires substantial refactoring. If another person opened my code with the intent of adding a feature he would HAVE to do something stupid. There's no way he'd be able to do it RIGHT, even if he was a GENIUS, even if he was my own CLONE unfamiliar with the codebase. Why, why for God's sake does this happen? Isn't there some methodology, some technology, some meditation technique, anything at all?!

How to keep my code simple?

Andriy Volkov
  • 18,653
  • 9
  • 68
  • 83
  • I suggest that you make this community-wiki (since there's no correct answer, and you seem to be seeking opinions on a broad topic). – Argalatyr May 03 '09 at 02:28
  • 2
    There's a book called Clean Code by Robert C. Martin, it goes a pretty long way of telling you how to keep the code clean, readable and simple. – Chris Vest May 03 '09 at 08:45
  • Ok..when something has "multiple answers", it is pretty much a CW. Yes, this is a discussion question. This should definitely be CW. – ryeguy May 04 '09 at 20:29

15 Answers15

12

As @neodymium points out in a zenly fashion, don't let your algorithms drive the code. To exemplify what that actually means consider the following griefing contraption in C# that you've written a week ago:

public void DoArray(string[] strArr) {
    string s = null;

    for(int i = 0; i < strArr.Length; i++)
    {
        if(strArr[i].Equals("Needle"))
            s = strArr[i];
    }

    if(s != null)
        Console.WriteLine("I've found " + s);
}   

It may take a while to find out what you've written just by looking at your code. But in the end you remember that this method looks for a "Needle" in the string array. You marvel at how ingenious your algorithm is but fret over the fact that it took you a minute or two to realize what it was doing.

Don't start making things clear by commenting!

Your first instinct may be to write comments to help your working fellows out, BUT STOP RIGHT THERE! Comments are nothing but an apology. Instead lets make ourselves clear in code… crystal clear.

In order to do that, try to think as if you've never seen your own code before. Force it by saying things to yourself like: "what is this" and "why is that". And then we'll start to refactor the thing. First simple refactor that we can do is to rename the method into something more fitting. How about:

public void FindNeedle(string[] strArr) { … }

What more can we do? We could:

  • Rename strArr to something more appropriate, like haystack.
  • Change the return type to a bool and change the method so it'll return when the needle has been found.
  • Move the Console.WriteLine(…) part of the method outside the context of this method so the calling code can do it instead like this: if ( FindNeedle(myArray) ) WriteFoundNeedle();
  • Use foreach instead of for.

The code may end up as following (your milage may vary):

public bool HasNeedle(string[] haystack) {
    foreach(string straw in haystack)
    {
        if(straw.Equals("Needle"))
            return true;
    }
    return false;
}

// Is called by the following:
if ( HasNeedle(strArr) )
{
    Console.WriteLine("I've found the needle!");
}

Refactor in small steps

Lots of things can be done in order to make your code more clear, understandable and readable and this is achieved by refactoring your code. Do your refactorings in as small steps as possible. Like moving or encapsulating logic and naming them to something much more logical and readable. Something as simple as a long and convoluted if-statement:

if ( strArr[pos - 1].Equals("do") && strArr[pos + 1].Equals("then") )

… can be refactored into something simpler by moving the logic-statement into its own method:

if ( CurrentIsSurroundedByDoThen(strArr, pos) )

There are lots of ways to refactor. I suggest you read up some of them, Martin Fowler has written a book about it but there is also an on-line catalog available with code samples and/or class diagrams. Choosing what refactoring you should do or not has to be decided under the assumption that it will make your code more maintainable than before.

In addendum

Remember keep your refactorings simple and do it with SOLID principles in mind.

Also if you start out your project by writing unit tests the right way then refactoring becomes something much more natural. The more you refactor, the clearer your code will become.

Community
  • 1
  • 1
Spoike
  • 119,724
  • 44
  • 140
  • 158
6

I feel your pain, buddy. The struggle for simplicity in complex systems is the struggle of software engineering. If you've nailed it, you're probably not working on hard enough engineering problems. And hard doesn't always mean complex, it may be "implement x by tomorrow to keep the sky from falling."

Towards simplicity ... TDD mentioned thoroughly, agree totally. TDD is a trick to keep code focussed on what it needs to do and no more. Re-factor frequently mentioned. Totally agree.

On simplicity vs complexity and working alone ... don't work alone on shipping code. Get code reviews every check in, and encourage code reviewers to rake you over the coals. That will keep you on track to make the right compromises and balances. Talk to someone about your code at least once a day. Rotate reviewers. My work is more lucid and just better with a teammate. Don't care how green they are. Actually, the greener the better to ensure clear code.

On working alone ... Working alone has its place in R&D, not shipped code. At best, lone cowboy projects make cool stuff that is terrible pain to maintain. Work done alone always needs a month or or two to re implement and re-factor into code maintainable by mortals and fix a few huge oversights. It's really really painful if that month or two hits you after you shipped the cowboy code.

Edit: On the detail side, I've found various books on Domain Drive Design extremely helpful in providing ways to create super clear code. DDD not applicable to every problem though.

If you do find the answer to the balance between simplicity and over-engineering ... well, I wouldn't even know what to do then. I suppose I'd get bored and find another line of work.

Precipitous
  • 5,253
  • 4
  • 28
  • 34
5

How do I keep my code simple?

Use code to implement the proper algorithms.

Don't let your algorithms write the code.

user79755
  • 2,623
  • 5
  • 30
  • 36
  • +1 That was pretty deep and philosophical (nicely done), can you elaborate? – Chris May 03 '09 at 02:39
  • 1
    +1 I'm with Chris though. Very to the point, but a short example (perhaps one with an algorithm driving the code, and another where the code drives the algorithm) would really get the point across and make it more 'tangible'. – Mike Spross May 03 '09 at 02:41
  • I think what @neodymium is saying is be intentional about the code you write and don't just write code to write code. You don't need a 12 page document to write a app, but some written idea where you're going is always good. It's like building a house and adding rooms as you go instead of having a blueprint. You're builder will love you because the house will cost thousands if not millions, and you'll never be done. – Chris May 03 '09 at 02:52
4

For me, Test Driven Development makes all the difference. When I write code without a test justifying it, I think of way too many scenarios, worry if the code will work, write tons of extra stuff to make sure that it will work, etc.

When I do TDD, the code comes out very simple because the tests made me write the right code, I wasn't as defensive, and yet I'm confident that it meets all the requirements because the tests pass.

Another thing which I find helps is to inspect the code of open source projects. Many of them have code which is easy for others to understand and modify, so it gives good insight into how to achieve that. One of my favorites in the that regard is JMock.

Yishai
  • 90,445
  • 31
  • 189
  • 263
4

One of the simpler ones and my favourite is to introduce explaining variables.

This:

if ( (platform.toUpperCase().indexOf("MAC") > -1) &&
      (browser.toUpperCase().indexOf("IE") > -1) &&
       wasInitialized() && resize > 0 )
 {
   // do something
 }

Becomes:

final boolean isMacOs     = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE")  > -1;
final boolean wasResized  = resize > 0;

if (isMacOs && isIEBrowser && wasInitialized() && wasResized)
{
    // do something
}
aleemb
  • 31,265
  • 19
  • 98
  • 114
3

Refactor often.

This is the only reliable way I have found to make my code simple. Sometimes you can't think your way out of complexity without starting to work on the code first.

Unknown
  • 45,913
  • 27
  • 138
  • 182
2

When you are considering if to add or to remove something, ask yourself if it belongs to the 20% of effort that delivers the 80% of the difference.

If it doesn't or you are in doubt, remove it. If what's removed is that important, it will become obvious soon.

Keeping telling yourself "do it simple" also helps. Go for simple and forget about correction, and correction will come and KISS you.

2

More or less unit testable means that it isn't unit testable. :)

If you can't easily write a test, without jumping through a lot of hoops or setup work, then that would seem to be a problem, IMO, and should require refactoring.

I try to follow three principles: Make it work Make it right Make it fast.

The first is just to get it working, mostly unit testable. :)

Then I refactor and improve the unit tests, complete them. This is the next step you should go to, as it sounds like you may be ignoring this concept. Also, try to remove any hard-coded values to make it more flexible. Put them in xml files, database or as constants in some common file.

Then, I profile and optimize the bottlenecks.

You may want to look at xetreme programming (XP) and understand the concept of implementing only the features that are needed, not trying to implement what you think is needed. It should simplify the code.

James Black
  • 41,583
  • 10
  • 86
  • 166
  • To add to this: conceiving unit tests as goals unto themselves make more intuitive the type of modularization that the OP is missing. Then you're writing separate functions to test whether the algorithm outputs a specific value, not writing a massive funciton to test whether it does a hundred things right at the same time. – Anonymous May 03 '09 at 02:40
2

I usually code with this quote in mind.

Occam's razor The simplest (explanation|solution) is usually the best one.

I'd suggest you post some of your code and ask for criticism and comments. You can learn a lot from other people. Try it :)

The Pixel Developer
  • 13,282
  • 10
  • 43
  • 60
1

I think that is a common problem. We all try to create elegant code and clean code, and sometimes we end up doing quick and dirty because we are under pressure and time and budgets are not infinite.

I try to follow DRY and KISS. "Don't Repeat Yourself" and "Keep It Simple Stupid". I also follow the "one thing" rule. A method, class, namespace, should only ever do one thing.

To be honest, I've tried doing a project from the test driven development point-of-view, and I found it really hard to get in that mind set, of you Red, Green, Refactor, but it really helped break old habits.

I've been a victim of tight coupling and had to rip things out into their own components, it was brutal and broke the application, but you can get caught up.

This is really a vague question, I agree, community wiki!

p.s. I don't believe something is more or less, it is either is, or it isn't

Chris
  • 6,702
  • 8
  • 44
  • 60
1

off the top my head: Build time into your schedule to refactor. Make refactoring part of the requirements to ship, not just an after-thought.

This would be based on wanting to deliver a product that is more maintainable, re-usable, and so future developers on it don't freak out and have to hack.

I really think the answer is making refactoring a bigger part of your life, and being disciplined about it. (I need to this as well! Similar problems...I think most of us are in this boat.)

alchemical
  • 13,559
  • 23
  • 83
  • 110
1

Only add things that are needed - don't future proof - only react to real problems

dfasdljkhfaskldjhfasklhf
  • 1,162
  • 2
  • 10
  • 18
0

You could try running complexity metrics frequently, decide what your upper limit is on complexity, and refactor when you exceed that limit. Complexity metrics are objective. They give you a way of quantifying how complex your software is and a way of measuring progress. Maybe you're beating yourself up over code that's pretty good. Or maybe code you think is simple scores high on a complexity metric.

I know that these metrics are crude. What really matters is the subjective psychological experience of complexity, not the McCabe complexity number, and the latter is a crude approximation to the former. On the other hand, I've found these metrics very useful.

John D. Cook
  • 29,517
  • 10
  • 67
  • 94
0

On the KISS theme:

1) Don't feel that you must fix all the tiny little bugs/features in your core code. Sometimes it is better to just leave them documented, and let the guy calling your code worry about how to work around them. (There may be a good chance he will never trigger that particular bug anyway, so any checks would have been a waste of processing.) This keeps your code small, fast and simple. If you want to make a foolproof-but-bloated version, code it separately from (on top of) the library code...

2) Don't let that other guy change your code! He can call your code, or he can extend your code, so if it is already doing its job, does he really need to change the core? Don't let him turn your SimpleAndSpeedySearchPage into a SuperAdvancedSearchPageWithCowbell, make him build the SuperAdvancedSearchPageWithCowbell by extending or calling your code.

If your team-mate does start adding stuff all over your neat little library, do the refactoring yourself. Pull your good code out to a superclass, and leave him just with his code calling yours.

Summary: Once your code is doing its basic job, stop working on it, and make it read-only! If you want to add "advanced features" and "bugfixes specific to your application", add them somewhere else. 100 simple classes are better than 10 bloated classes.

joeytwiddle
  • 29,306
  • 13
  • 121
  • 110
  • I followed this link from somewhere on StackTrace today, which is relevant: http://en.wikipedia.org/wiki/Single_responsibility_principle – joeytwiddle May 08 '09 at 17:56
0

Several things that come to mind:

  1. Try to plan ahead your implementation strategy - possibly on paper or discussing with a peer, before you dive into the implementation. It's likely that there are already a library functions available to handle parts of your problem.
  2. Write down the pseudo code that outlines the steps of your implementation.
  3. Try implementing the methods so that each method has a single purpose, or ties together other methods. It should be possible to see at a glance what any method is supposed to do. This is where TDD can help you to focus on small methods that are easier to test. Smaller methods also have specific names, so the name already tells you a lot. Also, some simple complexity metrics, as the one from this question can help you determine if your methods try to do too much.
  4. Don't stop when your implementation is working - try to refactor until it's as simple an maintainable as possible. Always ask yourself, if someone else would be reading this, would they understand it?
Community
  • 1
  • 1
pythonquick
  • 10,789
  • 6
  • 33
  • 28