views:

3242

answers:

46

I've been thinking lately about a few practices that I have kind of adopted. Not things you see listed all the times, but patterns that you've looked back and said "I'm glad I did that", then adopted for yourself.

I'm not really thinking of the ones you hear all the time, like "Refactoring" or any design patterns listed in common books either, but would include specific refactoring patterns that you find success with but don't hear about very often...

+15  A: 

Separate Data from Code. This is one of my biggest driving factors now--my ultimate mantra. I'm not talking about constants you declare in code as scalars, more about identifying patterns where you can extract data and place it in arrays and use them to instantiate collections of objects--eliminating repeated patterns.

This works very well on GUI code, I no longer would ever consider writing "new Button("Click Me")", because "Click Me" is data and should be from an array, as should all the GUI objects and, preferably, most of their locations and handlers.

This pattern allows some remarkable refactorings that are impossible without recognizing what is data and what is code.

Paul Tomblin
Yeah, I've been doing this a lot lately as I'm refactoring and extending a GUI. It's amazing how much cleaner it is!
sep332
I think you're describing the technique known as 'data-driven programming', discussed excellently in Eric S. Raymond's "The Art of Unix Programming".
Bobby Jack
Data driven programming is amazing. You really start to appreciate it when making games.
Cristián Romo
It also sounds like declarative programming - which I also strive to use wherever it makes sense.
Phil Nash
+18  A: 

My best change was using the single responsibility principle. Each class I write only does one thing so it is really easy to troubleshoot problems when they are broken down into small chunks. Sure I have many more classes now but by properly namespacing them and placing them in folders I can easily manage them. SRP encourages code re-use and really enables unit testing (and I think is a key concept for test driven development)

Kudos2u2
+4  A: 

Never pass around raw/native/library objects (specifically collections). I try to avoid passing around ANY raw java objects that I can't extend and aren't part of my "domain".

A class accepting a "List" gives no hint as to what you should pass in, a class expecting a "CarCollection" tells you exactly what to pass in, and the CarCollection class itself can ensure it's in a valid state with valid items, a List, even a generic List<Cars> cannot. Finally, you almost always want to add methods, when you use raw objects these usually become crappy utilities distributed throughout your code instead.

This has allowed me a remarkable flexibility when it comes to adding features that weren't planned for in the first place.

Paul Tomblin
Similarly, if your language supports generics, that method can just return List<Car>. You get the advantages of your specific CarCollection without having to implement it yourself.
Adam Jaskiewicz
Actually you don't. A generic collection has no place to add on Car related methods, and no ability to keep itself valid and enforce rules--maybe a single collection can only hold 20 cars, can't be empty or has to ensure cars are only removed() when isParked().
Bill K
I think there is a place for raw (parametrized) collections like List<Car> for simple cases. On the other hand, what you are suggesting is very similar to the Aggregate pattern (from Domain Driven Design), so I wouldn't create a CarList class, I would try to model the domain and perhaps end up with a Fleet class (Fleet: a number of ships, vehicles or aircraft operating together or under the same ownership) that implements all the business rules related to that aggregate.
Sergio Acosta
+3  A: 

Errr... document code!

Especially the public API, with a clear definition for each function, and the the list of authorized limit values for parameters.

Even though no other documentation ends up being written, that much can save me when I have to use (or to make other use) my code!


Interesting: the recommandation about

"When commenting: Tell me WHY you're doing something, not WHAT you're doing"

picks up a lot of vote, but this more general recommendation does not.

That would be consistent with what I see amongst the developers I work with:
no one document its API... (or at least, not many ;) )
(I am referring to public API comments, not to internal bits of wisdom left within a code which are simple comments, not documentation)

VonC
+31  A: 

Simplify your design requirements. Trying to bite off too much for version 1.0 has helped kill a few of my own projects, while my successful applications were built with the minimum requirements in mind. Try to figure out the very core functionality you want to accomplish, and save the rest for later. You'll thank yourself when the simple stuff turns out to be not so simple in implementation. Also, by getting your product out faster you'll have a chance to get more feedback from users, which will help decide which "extra" features are worth developing or not.

Marc Charbonneau
This is a big deal, especially for side projects.
Brian MacKay
In other words it is "release early, release often" mantra.
J.F. Sebastian
Excellent advice. I launched a site recently wherein the core feature was a large user-searchable database. Due to time constraints, I launched with only a basic search implemented. The super duper awesome advanced search got pushed to the feature wishlist to my great embarrassment... I was quite surprised when user polling one month post-launch revealed the advanced search to be the least desired proposed feature! I'm glad I launched rather than postponed things to code something most people didn't want!
Andrew Heath
+24  A: 

Never Pass English Language Up To the GUI Low level code should not be producing user visible strings. Instead, they should be passing up objects with codes that can be translated and formatted at the uppermost level. This way, if/when you internationalize, you can do it in one place instead of hundreds. Also, it means that in an environment where the View level is separate from the Model, you can have more than one Viewer using different languages. For instance, your movie theatre management system could be simultaneously viewed by the theatre in France, the regional Network Operations Center in Spain, the studio in Hollywood, and the support site in Rochester NY.

Paul Tomblin
+61  A: 

When commenting: Tell me WHY you're doing something, not WHAT you're doing. What you're doing is already there plain as day, it's called your code.

Matty
There are times when "what" is more informative than "why". If you are implementing a complicated algorithm, its useful to know what the algorithm is *supposed* to be, to compare against the reality of the code, but I'm guessing you are referring to the "add 1 to i" type comment and not the former.
Brian B.
This is repeated so much here that I'm coming to hate it. Sometimes things don't tell you what they're doing in the code because it isn't obvious, or it is complicated. Usually WHY is just as obvious as HOW. Comment only when needed, or when something is mission-critical.
The Wicked Flea
This is a wiki, so I think I'll select the #1 voted comment. Thanks everyone!
Bill K
+6  A: 

Code with sources managed under Version Control System!

That way, any refactoring is less an adventure and more a planified effort, with always the possibility to go back or to compare to a stable state.

VonC
+21  A: 

Always assume that some poor schmuck will have to maintain your code.

  • keep it simple,
  • easy to understand,
  • segmented in manageable and logical chunks

Remember, you might be the schmuck that has to maintain the code, give yourself a break, take no "shortcuts", dont use "clever" side effects, avoid cryptic variables, no unrelated comments...

lexu
Also, assume that the poor schmuck who will have to maintain your code is borderline psychotic and knows your home address.
Paul Tomblin
ahh schmuck is a great word when used in this context.
Nathan W
I always seem to be the poor schmuck..+1
discorax
My middle name is schmuck (so it feels), boy do I hate to edit my own code. I tend to be embarrassed "I wrote THAT"?
lexu
so if I write maintainable code than when I have to work with someone else's code it will be maintainable? not really :( plus you should not be afraid of other people.
01
I'm so hugely in favor of this because the poor idiot who has to maintain my code is, often as not, me. Six months later, when I've had a chance to forget most of it.
BlairHippo
+1  A: 

Thread-safe by default. All of my (Java) code is thread-safe unless explicitly documented otherwise. It takes a bit more thinking and sometimes makes the code more complicated, but I go to great length to consider concurrency in every nook and cranny of my (newer) code bases. To this end, the java.util.concurrent package is a blessing.

This pactice, I believe, makes me write better code. Not only do I consider the concurrent failure modes, but I think that simply thinking about the code more leads to fewer mistakes.

Christian Vest Hansen
+5  A: 

Fewer features, fewer configuration options = usable software = maintainable code = used software.

simple. bye.

Blankman
"fewer", "FEWER", godammit! ;-)
Bobby Jack
Gotta agree: less sand, fewer grains of sand.
Peter K.
there ya go grammar nazis. :)
Epaga
no, I like less better: http://gettingreal.37signals.com/ch03_Less_Mass.php
hasen j
+22  A: 

In OO languages, keep simpler data objects immutable. This builds upon Paul Tomblin's point about keeping code separate from data. Simple data objects are like bits of hard metal currency passed around the more complex agents in your program, in that they're tangible and definite. They're unlike currency in that thin strings attach them to any object that wants them, for as long as the complex objects want. You wouldn't want someone grinding the "Five Cents" off your nickel and etching "Ten Cents" in its place; nothing should change what your complex objects assume about those bits of data.

Paul Brinkley
+6  A: 

In OO languages, complex logic objects should have their API be as small and restricted as possible, and what is there should be documented as a contract. This cuts down on maintenance like you wouldn't believe at first. It makes unit tests for those objects possible. The alternative is objects that expose a smorgasbord of methods to others; they may as well be festooned with billboards shouting "Poke me!", and you'll never know what their state is at any given time.

The corollary to this is that you'll be surprised how often extending an existing class turns out not to be the right choice, in the case of logic objects. (It's significantly more frequent for simpler data objects.) For example, queues are one of the simpler data structures out there, and look a lot like lists - but they are not lists. If the list allows random-access insertion and removal, and sorting, and the queue inherits it, then it will have that too, and risks having other objects use it as a list unknowingly. A queue should contain such a list, perhaps, but not extend it; it should have a totally different API - limited, tested, and well-documented.

Paul Brinkley
+11  A: 
  • Writing unit tests (first)
  • One (or zero)-step compile/deploy/test cycle
  • Know your tools (editor, build-system, vcs)
  • Use a VCS
  • KISS (keep it simple)
  • Know how the paradigms of your language (object orientation, functional etc)
+6  A: 

This has to do more with source control than code writing, but it impacts the way I develop my software:

Coherent and self contained source control commits:

Each time I'm about to develop a new feature or fix some bug, I plan the following two or three programming micro-tasks, and make sure each one is fully contained in a source control commit. Do not mix changes that are not related on the same commit.

Of course I'm talking about commits to my local branches. When pushing to the central repository, you should always commit full bug fixes and working features.

Sergio Acosta
+1  A: 

Use meaningful, relevant, human-readable identifiers for variables, methods, parameters and so on. Arbitrary abbreviations make your code much harder to read and understand.

flesh
+5  A: 

Don't overengineer. Engineer it just enough to get the job done and to be able to maintain it.

Paul Nathan
+7  A: 

Classes are fine, but don't start there. Instead, analyze flow of information. Where does it come from, where does it go to, how is it encoded, and when is it processed?

For example, if some of the information only changes once a week, possibly you could use a partial-evaluation pattern (i.e. code generator). This can be a divide-and-conquer strategy because the code generator only deals with part of the problem, and the generated code only deals with the remainder. Sounds funny, but it can really simplify things (as well as run a lot faster).

Mike Dunlavey
A: 

Top Down Programming. Some may call it TDD or BDD, but I simply always trying to be focused on what's currently required. Well, my fingers keep trying pulling me down to the metal by saying: while you're here at this peace of code add some special handling etc., so I'm not giving up and telling myself YAGNI YAGNI move back upward to see what's the next thing that is absolutely necessary. Just my 2 cents.

robi
+4  A: 

1) Be Consistent. Nothing is harder to change or understand than inconsistent code.
Bonus: Comment WHY and perhaps HOW, but leave comments about defects, who, and when the changes were made to the SCM.

JGeiser
I would just add that all things being equal consistency should always win. If every other GetName method returns only the first name, do not decide that your GetName method will return the full name because it is easier. You (or someone you know) will regret it.
Brian B.
+3  A: 

My personal list:

  • Understanding the bigger IT and domain pictures before writing code.
  • Doing a lot of thinking about what could go wrong, and what's likely to go wrong.
  • Paying my development taxes.
  • Sprinkling assertions liberally across my code and also code I have to maintain.
  • Using Try...Finally much more than Try..Catch.
  • Many comments explaining why certain design and implementation decisions were made.
  • Not sharing state between threads.
  • Poring over the documentation of a type before using it.
  • Refactoring constantly for simplicity and understandability.
RoadWarrior
A lot of what you wrote here should have been done by some project analysts even before you try to write a block of code..
Skuta
Good developers should be doing this regardless of what the BAs do. As for BAs understanding what could go wrong, that's very rare.
RoadWarrior
+14  A: 

FIRST write a test that goes red. THEN write the code that makes that one test turn green. Refactor as needed. Rinse. Repeat.

Epaga
+6  A: 

Seperate your concerns!

Do not create "UBER" classes which contain a lot of knowledge of your domain, spread it out in logical part, so that each class has a function in the system.

Also try to decouple allot, so you can easily plug in your test/mock implementation for testing purposes.

Davy Landman
A: 

Keep the data structure loose and simple. Don't rely solely on notifications to keep various parts of the data tightly in sync. It's too hard to prove they're right, and it takes a lot of programming energy. Instead, have diff-style routines that you call once in a while to clean up inconsistencies.

Mike Dunlavey
+2  A: 
  • Increase Cohesion + Reduce Coupling between modules within design (eg. objects, threads, any functional blocks)
  • K.I.S.S. principle
Prembo
+1  A: 

Don't start without a design document.

Skuta
That sounds like a pragmatic answer to me ....
ceretullis
+10  A: 

When approaching other people's code I've taken to assuming that I am the biggest idiot on the face of the planet who has never seen a single line of code in his life. That way, whenever I get the urge to "correct" some line of code that seems pointless and wrong, I think to myself

"You know, I'm pretty dumb, and the person who wrote this probably isn't, so maybe I should find out why they wrote it before I go mucking around in here".

Often I will discover that there is in fact a reason for that line of code that I had never considered. Not to mention the number of times someone has "simplified" some piece of my code, only to discover the week before it releases that the code was actually required to handle that one case where if you hit the "o" key at the exact moment that you press OK while simultaneously playing "Eye of the Tiger" in iTunes via your iPod you will crash the application (which is relevant because your customer is a huge Survivor fan who obsessively presses the first letter of whatever dialog button he is clicking on - it happens).

Brian B.
This is a really good reason to comment your intention too. They can often indicate to the next person that you aren't (or are) a complete idiot so the next person can react accordingly.
Bill K
This is a great principle. I've run into many problems in my development career when I've assumed exactly the opposite.
Kyralessa
+1 for the story, and for inspiring me to play "Eye of the Tiger" in iTunes
discorax
Strangely enough, I always start off with that attitude - but pretty quickly I'm forced to find that it's usually the exact opposite. So, while I won't exactly recommend this as a rule, sometimes it's necessary to DELETE THE OLD CODE. If you know you're justified, and AFTER you've checked. Then test
AviD
Yet another reason to capture rationale behind changes! If not right there in the code via a comment, then at least in the commit message when the change got added to the repository.
Sarah Vessels
+3  A: 

Generalize the problem, write code that solves the general problem and use it to solve a specific case.

supermedo
Or use the Agile methodology of write the specific way twice, then refactor into the generic.
Bill K
+4  A: 
  • Documentation before (What)
  • Coding
  • Documentation after (How) Unit
  • Testing (Validation & Verification)
Daok
+2  A: 

When doing design, Create a GUI as one of the first parts of your design. On a whiteboard perhaps, or in simple front-end-only code. This is a GREAT way to communicate with your customers and find out if you are on the same level.

It also becomes about the best project specs you're likely to get.

I'm not saying code before design, the GUI is not actually the first code you write, it's just part of design that you may throw away or redesign when you actually get around to writing code.

Bill K
But showing the progress to customers in the terms of GUI makes them have unreasonable expectations about the back-end development. You need to make sure how complicated and time taking the back end development is going to be.
Ravi Gummadi
+5  A: 
  1. Use Subversion or some other sane version control system.

    • Use branching when doing risky work that might kill the whole build.
    • Be sure to carefully merge your risky work back into the trunk.
    • Be sure that you have a Subversion "hook" setup to run unit tests on code and make sure it passes all tests before checking the code in.

      (this way you know when you've broken something of somebody elses)

    • Be sure to comment in detail what the change has done on a check-in. And for pete's sake make sure your commits are atomic! (A.K.A. checking in all files that you changed relivent to the commit at once.)
    • Another good rule of thumb is one working-copy to a task.
  2. Use Unit-Testing on everything (even Javascript)

    • In this way you know what parts of your code work, and what parts of your code don't work.
    • (redundant yet very important) Also if you unit test you'll know when you've broken someone else's code, and thus you'll know when the two of you need to discuss it.
    • Unit-Testing also makes sure that you code is decoupled from other parts of the system (ensures that you've made good use of interfaces).
  3. Use a Dependency Injection Framework (okay not for JS). This is related to the bullet point above as well, and can allow you to do incredible things like using Aspect Oriented Programming to add and remove log statements very easily, also it helps when handling SQL transactions. (making sure your begin and commit statements are run at the right place in your code)

  4. Keep your team small, but highly skilled.

    • Too many cooks in the kitchen spoil the broth...
    • Too many people at the video store never pick out a movie...
  5. Communicate with your team as much as possible!
    Gone are the days when one could hide away in a cubical and just work on your project, it's all about team work now.

    • Have meetings, to discuss what the "team" as a whole is going to do.
    • Take notes in a shared wiki if possible.
    • regular at least weekly code reviews with your peers, discuss which design patterns are going to be used, and make UML design diagrams!
  6. Keep in time with the rest of the world...

    • Or as I like to call this one, "It's really hard to get help with Visual Studio 97' when it's almost 2009!"
    • Keep your frameworks/libraries, tools and compilers up-to-date, as it will make the questions you ask about them much less awkward, and more likely to get answered.
leeand00
+6  A: 
  • Copying and Pasting == Time to Refactor

  • Make your code as simple as possible, but no simpler.

  • Code defensively.

  • Don't try to be smart or clever.

  • Let go of your ego.

  • Cheerfully accept criticism of code that you write.

  • Learn from your colleagues - even the newbie intern has things to teach you.

Terry Donaghe
Copy and paste isn't really a problem if you are bringing in code to reuse from elsewhere. Within your project, it's criminal--The only time it's ever acceptable is if it's refactored before it's ever checked in.
Bill K
I believe in DRY big time. Then when you are looking for a problem, you know where to look for it. I really hate having to search though a big ball of mud! I've already worked on code bases where the people loved to copy and paste. They had to get larger hdd's to copy over their 1GB code base!
leeand00
+1 for "Learn from your colleagues - even the newbie intern has things to teach you."
pierr
+1  A: 

There's more than one way to test.

When I learned about refactoring, I tried to write good unit tests. But I often found pieces of code that were resistant, but that I really, really wanted to improve...and so I'd end up changing the code without any tests in place. And of course I'd often break something in the process.

I learned two things:

  1. You have to have some way to test, even if it's not a perfect little NUnit test. It might be text in the Debug window, or a script of click here-type this-click there, but you've got to have something to tell you whether the code still works.
  2. If you can't figure out any way to test the code, then you don't understand it well enough to change it successfully. So don't try!
Kyralessa
#2 resonates well with me. But if the code in question is truly problematic you should keep at it until you do understand the code well enough to design a test for it.
Dan Malkinski
Well, if you *have* to change the code, then of course you'll just have to keep working at it until you understand it. Sometimes a "scratch refactoring" (which you throw away when you're done) can help in this; just remember to throw it away!
Kyralessa
A: 

I agree with Joel, Make wrong code look wrong.

The speed difference between reading code formatted in a constant way and code that isn't is ridiculous. Assuming no one went out of their way to make the inconsistently formatted code obscure I'd guess consistent formatting reads over three times faster with a much higher likelihood of detecting a problem.

CrashCodes
I didn't like that example at all. It used bizarre naming abbreviations and patterns you have to memorize. Why not just create an UnsafeString object so you can .getSafe() string out of it? Why be cryptic and difficult to read when you can clearly state your intention?
Bill K
I don't think the naming abbreviations were bizarre within the context; and what you are suggesting is just an (probably better) implementation of the point Joel was trying to make.
CrashCodes
A: 
  1. Read Code Complete
  2. Read Code Complete Again
  3. Remember Code Complete while you program
Holograham
+3  A: 

Consistent object naming and well documented source code. It's not a problem of anybody else reading your code after some months, that person might be you and sometimes you don't know why you did something that way or how something works.

Angel
I agree, if you take the time to document things, the coder you save might be yourself! (But it might be somebody looking at your code 10 years later too!)
leeand00
A: 

I'd say take the time to think before doing something.. I often come up by doing it simpler/faster than I would have done it.

Also, I try to write clean code even in langages where good practice aren't always forced.. For instance, in python or bash :D

+12  A: 

I try to live by the words of Brian Kernighan:

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

I summarize to my co-workers as "Clever code kills".

MikeHoss
A: 

Well designed interfaces are crucial to managing complexity. When the interface between two pieces of code is ill-defined or "loose" things get out of hand very quickly. I've found that perl is especially good a letting you create an entire castle out of matchsticks... In any language, you have to be diligent and protect your code with a clean interface.

Andrew
A: 

design a really well and easy to understand data structure/db structure, then even if your code is whacked, it's still pretty much self explanatory.

melaos
A: 

Personally, my best "get things done efficiently and organized" is to write the problem's solution out. This takes two forms:

  1. A high level to-do list, crossing off items as they are slain.

  2. Actually coding it on paper first.

It almost seems like I think through problems more completely when I code on paper first than when I'm hacking on a screen. Your mileage may vary.

ben mcgraw
+6  A: 

Reduce the number of possible 'states of being' that can exist

Ensure objects are initialized and consistent the instant they are constructed and stay so until they are destructed.

If you know your objects members are all initialized and valid while the object exists you can write methods to rely on that without worrying.

I often see code where the author doesn't recognize how important object lifetimes are. They expect you to construct an object and then call various initialize methods on it to get it bought properly to life. This means their code is littered with checks to see if members are valid before they're used. All these checks reduce the code to an impenetrable mess.

Scott Langham
I would add: reduce the number of invalid states. The more invalid states there are, the more you have to worry about getting into them, and getting out of them.
Mike Dunlavey
A: 

One thing which I read somewhere is "Always select the SECOND workable solution for the problem".

The first solution that you get to most of the time is probably the easiest one but that will come back to bite you in the future.

+1  A: 

Defensive programming. I really like to use defensive coding techniques in the style of Design By Contract. It's cheap to put assertions to verify "impossible" conditions, they document your code, and save your live failing fast when something impossible happens.

The best reference I have in the subject is chapter 8 of McConnell's Code Complete Book.

neves
A: 

This is a performance analysis practice that leads to better coding practices.

Somehow the idea got around that you can't dynamically analyze performance without a profiler. Not so.

When code is being sluggish, use the simple random-halt technique to find things that take time that are not necessary. You can do this in the normal process of running the code under a debugger or IDE.

Especially in large software, you usually find that, after you fix a few "low-hanging-fruit", the fundamental reason for slow performance is over-design with way too much data structure.

This leads to a better coding practice of minimizing data structure and not over-designing.

Mike Dunlavey
+1  A: 

Every time you're tempted to write a comment about some long-range assumption in your code, assert it instead.

dsimcha