views:

1362

answers:

20

I've had several programming jobs. Each one with 20-50 developers, project going on for 3-5 years.

Every time it's the same. Some programmers are bright, some are average. Everyone has their CS degree, everyone read design patterns. Intentions are good, people are trying hard to write good code but still after a couple of years the code turns into spaghetti. Changes in module A suddenly break module B. There are always these parts of code that no one can understand except for the person who wrote it. Changing infrastructure is impossible and backwards compatibility issues prevent good features to get in. Half of the time you just want to rewrite everything from scratch.

And people more experienced than me treat this as normal. Is it? Does it have to be? What can I do to avoid this or should I accept it as a fact of life?

Edit: Guys, I am impressed with the amount and quality of responses here. This site and its community rock!

+10  A: 

20 to 50 developers is probably the problem. That is pretty high and would need a lot of management and resources to keep everything in check.

I would consider splitting the project up into smaller reusable segments. Abstract certain layers away from the core system.

NathanE
Well, 20-50 developers is what is needed in these cases as the project are relatively large. Reuse of modules is not _real_ as a company has no other products. Blackbox module testing is hard as you have to emulate the rest of the system for each module. But thanks :-)
Yoni Roit
Could you elaborate on what it is? That seems like a huge project, especially for it to be "undivideable"
Tony Arkles
+1 The word "reusable" in the answer seems to be causing confusion, you could even remove it. Splitting up will help manage complexity because it will be harder for module A to be coupled to module B in obscure ways. If the interfaces are devised carefully.
MarkJ
+9  A: 

Create "firewalls" between different areas of the code. You do this by defining different areas or layers of code, and defining a single API (in Java, this is usually done with an interface) that each layer responds to. There should be bare-bones intefaces or classes that the API uses, but which "know" nothing about the internals of those layers. For instance, the gui should not know or care how you store data, and your database shouldn't know or care how the data is presented to the end user.

These APIs don't have to be cast in stone - you should be able to add things as necessary, as long as you make sure you aren't polluting the firewalls.

Paul Tomblin
I understand and respect what you're saying, but this isn't always possible. If I provide infrastructure to module A, then module A depends on it for correct working/testing. Implementing a mock for this infra would be like implementing infra once again for the purpose of testing.
Yoni Roit
Correct but this usually happens in practice. Sometimes the effort can be minimized by using mock frameworks.
Andrei Rinea
A: 

More code reviews and perhaps code ownership.

If you just hack some random code then you don't care as much as for the code you "own". If it's your responsibility to maintain one module of the project, you want to shine.

And code reviews is a time when you show your code.

stesch
+16  A: 

Ruthless diligence combined with constant unit testing is the only way to prevent spaghetti code. Even then it's only a band-aid solution. As soon as you stop paying attention out comes the pasta.

Very often I find that spaghetti code is introduced because someone is just plain being lazy that day. They know there is a better way to do it and just don't have the time. When you see that happen there is only one thing to do.

Call them out on it and ask them to change it

I find that pointing out the better way during a code review is usually enough to get people going. If they check it in and I feel strongly, I'll refactor it myself.

Do I occasionally come off as a little bit excentric. I'm sure I do. Frankly though I don't mind. I'm not a jerk about it and approach this in the best possible social manner. However letting bad code get checked in pretty much assures that I am going to have to debug it at some point in the future. I'd rather take a little flak now and get the right code in.

I also feel that a culture of unit testing also helps prevent spaghetti code. It's much harder to unit test spaghetti code that well factored code. Over time this forces people to keep their code somewhat factored.

JaredPar
I really like what you're suggesting. To the point of fantasizing about hiring a special person to a group whose only job responsibility will be checking commited code and calling them out on it. Maybe it's a sound practice.
Yoni Roit
If only 1 person does the calling out they will be resented and people will work around them. Try rotating the responsibility among team leads and later everybody who codes. Code Review is everyone's resposibility, else when the reviewer gets fired, bad habits will come back.
hromanko
Good point, hromanko.
Matt Refghi
"eccentric" :-)
Khb
+1  A: 

You have to follow software development practices closely. There have to be code reviews, and unit test that constastantly make sure that the updates are affecting other things in the system. 20 - 50 devs is a lot, but it can be done. Implementing good processes is the only thing that will save you in this environment. Enforced coding standards are also key.

Kevin
+2  A: 

Refactoring

Strive keep the design as clean as possible. This is not easy, but it's worth the effort.

philippe
+3  A: 

It sounds like many are not following some basic tenets of encapsualtion and good design.

Keeping things isolated and unreliant on other parts is essential to avoid the problem you describe. You might need some higher level designer or architect. This is a typical scenario where people have justified some draconian processes and change management. (I do not advocate that)

You need to avoid dependencies and interrelationships and define and use public interfaces only. This of course is an oversimplification, but you will probably learn a lot by some metrics on your code - complexity of classes, public methods, UML diagrams built from reverse engineering the code, etc.

Tim
+1  A: 

tracking defects and performance of various parts of the system will allow you to identify problems. As systems are changed poorly designed or written functions or modules will have a higher rate of defects. When a "problem" module is identified a decision can be made to rewrite the module (NOT the application).

Jim Blizard
+7  A: 

I think the main point is when you say

you just want to rewrite everything from scratch

Just embrace it.
Use as many unit tests as possible, then let refactoring be a common practice.
Automated and unit test will ensure that changes will not introduce regressions; devoting a certain percentage of your time to refactoring old code (and this means, less new features!) ensure the existing codebase will not get old, or at least not so fast.

Roberto Liffredo
Refactoring has to mean *more* new features not less, otherwise it's a bad idea. What it might mean is, no new features *right now*, but *soon* more new features than if I just dived in now. Technical debt is a useful concept.
MarkJ
+2  A: 

I do not think it is normal. It is really hard to fight this thing when it was there for a couple of years.

The only way to avoid it is to change the attitude:

“The attitude that agile developers have toward the design of the software is the same attitude that surgeons have toward sterile procedure.  Sterile procedure is what makes surgery possible.  Without it, the risk of infection would be far too high to tolerate.  Agile developers feel the same way about their designs.  The risk of letting even the tiniest bit of rot begin is too high to tolerate.” Martin C. Robert “Agile Principles, Patterns, and Practices in C#”

I highly recommend to look into this book for advices. It names all "design smells", the reasons of their existence and consequences of leaving them. May this will help you to persuade your management that current situation is not appropriate.

Good luck!

Dzmitry Huba
+3  A: 

I think the loose coupling you can get with full-blown use of dependency injection is a technical feature that can help a lot. When you break apart the pieces of the application you're less likely to get spaghetti resulting from "interesting" re-use.

You may be headed for excessive fragmentation instead, but that's another issue and less of global structural problem.

krosenvold
A: 

Start creating unit tests, this will help you decouple your code and avoid follow up errors on bug fixes. It you have good coverage it will make it easier for you to remove unused code as well.

TT
A: 

Continuous refactoring. You have to refactor as you go, especially at the design level. When you see broken code or design, be prepared to fix it. This is often a case of fixing something that isn't broken, per se. Except that it is... it's just not manifesting it's brokenness... yet.

Software Monkey
+1  A: 

No

:)

+6  A: 

Code reviews, coding standards, and firm policies.

The following is applicable to our shop - since I don't know what sort of shop you have, your mileage may vary. While moving to Team Foundation Server, a large part of our focus was on maintaining code quality - or at least helping to maintain quality in any way possible. Some examples of what we are adding:

  • Code Review Workflow - Enforces code review as part of the process. Contains a policy that will prevent check-ins from happening if the code has not been reviewed.
  • TeamReview - Makes code reviews less painful by providing a complete "inside the IDE" experience.
  • Check-in Policies (in general) - Many cool goodies available for control the flow of code. Things like making sure that public and protected methods are documented prior to check-in to making sure that no work can be checked in without a corresponding work item.

Like I said, if you are using a different platform, maybe the tooling available and what you can do is different. But don't rule out tooling to help in any way possible. If you can use it to improve, control, and audit your workflow and the items that move within it, it should be at least worth considering.

Remember, any changes in process are going to involve pushback. The way that we have helped ease this is to build the policies into the training for the transition from our old version control / defect tracking system.

joseph.ferris
A: 

The biggest problem in the software industry is that the quality of programming code is viewed as a subjective issue. Without some well-defined metric, just being neat and tidy, and following the conventions isn't enough to ensure that the quality is acceptable.

There are attempts to change this, but they are unlikely to get sufficient interest or acceptance primarily because the long established culture of programmers is in trying very hard to stay away from anything that resembles engineering. The "pure art" philosophy of programming means that your 20-50 developers are all going to flail at the code in their own unique fashion, so that no matter how good the individual coders, the sum total of the group's effort is always going to be "a big ball of mud".

To avoid this, either get all of the coders on the same 'page', make normalized code part of your convention, or chase after jobs were the development teams are smaller (1-3 people) and you're the big kahuna. Someday the big teams may find a way to build better stuff, but until then even the best of them are extremely lucky if they can just get close to 6 out of 10. We build low-quality software because that's what we've set up our industry to do ...

Paul.

Paul W Homer
+7  A: 

I believe the key to avoiding code rot lies in sound bottom up design and implementation methodologies (I believe it so strongly that I named my business - Think Bottom Up - after it!). The tools of choice here are:

  • Programming by contract
  • Layered design
  • Focus on decoupling
  • Always build with reuse in mind, looking for generic solutions
  • Keep frameworks lightweight, simple and focused

As suggested by other respondents, you need to catch problems early. With green developers, this means mentoring (peer programming is great here) and reviews (code and design reviews). With more senior developers, this mean vigilance.

Most of all, do not be afraid of refactoring. If refactoring scares you, you're already sunk. If refactoring is seen as "bad", then there is something wrong with your business.

When you fix something, fix it properly. I use the term "fux" to describe a fix that was done the wrong way as it just "fux" you code base.

Cheers,

Dan

Daniel Paull
+1 for "fux". :))) It's very innovative and intuitive...
Andrei Rinea
Why thank you - I know I can always fallback on stand-up comedy if this "software engineering" gig doesn't work out. I would have preferred a +1 for thinking bottom up, but hey, it's all good!
Daniel Paull
maybe nerd standup comedy. perhaps you should start a blog :)
Jesse Pepper
Nerd comedy? I believe my style is "Comedy for the discerning geek."
Daniel Paull
A: 

I'm in a similar situation now. First thing I did was to add Spring Framework to the project. You can use DI to break up some of the dependencies between the classes. Then I just started writing unit tests for each methods of the most used classes. I set up Hudson for CI on my work laptop. Every change that is committed to the repository I do a complete build. If the build is broken I send email to everyone on the team saying I can't build the project and to please fix your changes. Start there and slowly refactor project.

jmclurkin
+3  A: 

Don't allow code to be committed until at least two pairs of eyes have seen it.

Norman Ramsey
+1  A: 

Shore and Warden's The Art of Agile Development is a great book, with a section on "Applying XP to an Existing Project" (in chapter 4). Projects get worse over time unless you fight hard: overcoming such technical debt is hard and will make it increasingly difficult to ship acceptable releases. The only solution is to reduce the rate at which you deliver new features, and spend the time saved improving test coverage and refactoring.

Usually, projects don't have much test coverage, and don't have the option of running a 10 minute automated script that will build and exercise your code pretty thoroughly. Instead, most code is structured so that it is hard to test. The best option then is to add simple test coverage where you can, while starting to refactor with a view to making the code abstracted such that it is easier to test.

Though the team will need to spend time improving the code to make it clean and testable you probably won't be able to stop delivering for the time it would take "finish" the cleanup. So, you have to do it step by step while also adding new features. That's okay, pick the worst areas first, and don't expect obvious benefit right away. Keep at it, because eventually you'll get there. Don't listen to the voices that say all large projects are bad.

In short, spend a little time each week tidying up, and make sure the code is better next week than it is this week.

Dickon Reed