views:

628

answers:

14

--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?

+2  A: 

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
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
+5  A: 

How do I keep my code simple?

Use code to implement the proper algorithms.

Don't let your algorithms write the code.

+1 That was pretty deep and philosophical (nicely done), can you elaborate?
Chris
+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
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
+1  A: 

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
+1  A: 

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
+2  A: 

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
+2  A: 

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
+2  A: 

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
A: 

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
+2  A: 

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
+9  A: 
Spoike
A: 

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
I followed this link from somewhere on StackTrace today, which is relevant: http://en.wikipedia.org/wiki/Single_responsibility_principle
joeytwiddle
A: 

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?
pythonquick
+1  A: 

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

BPAndrew
+1  A: 

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