Ask HN: What is your best advice for a developer to write better code?

146 points by gdaz ↗ HN

200 comments

[ 2.9 ms ] story [ 257 ms ] thread
Read more good code and emulate after you fully understand it.

There are lots of projects that are good examples.

https://news.ycombinator.com/item?id=4331688

The problem is that good code isn't so obvious that it is good, its just kind of there and understandable (as it should be). Bad code on the other hand is obvious that its bad, as it takes you ages to understand what is going on.
Which is why I usually generalize this advice to "Read code". The majority of what you read should ideally be good code, but seeing both a good way and a bad way of doing the same thing is sometimes necessary.
1.) Comment! Not only does it make the code easier to interpret, but it can also bring logic errors to light as you think through why you implemented something in a specific fashion.

2.) If the language supports error handling (try catch/except) learn it and use it.

3.) Use descriptive variable and function names. Even if it means more typing, the readability is worth the effort.

4.) Use proper code style. Most languages have conventions for style that include what should be capitalized, whether to use camel case or underscores etc.. Learn them and use them.

5.) Write a lot of code. I don't think that there is any real substitute for experience.

In re: comments, I've found that any time I would write a comment, I try and see if I could re-write the code to make the comment redundant. It is possible to do this in a surprisingly large number of cases.

As for #5, practice makes permanent. Writing lots of poorly thought out code just ingrains bad habits.

Sure self explanatory code is the goal. But in the real world this is never entirely possible. File parsing for example. Without seeing the file being parsed it can be very hard to interpret the code parsing it. Or maybe you are interacting with other software that doesn't always respond predictably to your code and you need to explain why these calls are made in this order.

I agree that practicing bad habits is unhelpful, but I still maintain that the best way to get better at programming is to program, particularly by writing and contributing to open source projects in your spare time.

I'd caution not to use commenting as a crutch, though. Code really ought to be readable and easy to follow all on its own. I don't think code should be littered with comments just narrating what's going on, since those can get out of sync easily when code is modified. The code itself ought to be descriptive enough to "tell its own story", so to speak. Comments ought to explain why something is happening if the author feels the code may be unclear.
Agreed. If I find myself typing a detailed comment past TODO FIX THIS TOMORROW, I refactor. It is rare code should need a comment. Pulling a clever trick to trick the JIT into inlining a function call? Cool add a comment. Just loading junk form a database to show a user? Should not use a comment.
Study the source code of major open source projects & try to contribute - you will see techniques for better code in their natural habitat, and through code review.
Write a lot of it, and keep a critical eye. Try to find the issues that tend to come back to bite you and avoid them in the future.

Dealing with the consequences of bad code is a very good teacher :D

EDIT: Also take notice when other people's code cause you trouble, what can you do to save trouble to a future colleague?

(comment deleted)
(comment deleted)
I see study open source code and read good code, but what I think is more effective is contribute more to open source. Coding is one of the few professions in the world where some of the leading experts give away great samples and advice on a regular basis in the form of open source. Start an open source project for a need you have or submit a PR for something you've always wanted in a project you've used. Generally the comments on PR's are a great place to learn about ways people think through problems and how they're implemented.

My best piece of advice if you take this road though is that you need to check your ego at the door. The only way to truly learn to be better is to take criticism learn from it and keep moving forward.

Don't be afraid to submit PR's because of scrutiny because it will only prevent you from learning.

In my experience as a dev the solutions that you think are so simple and maybe even hacky sometimes turn out to be just steps away from a far more elegant solution than anyone else has thought of and you're hurting yourself and in many cases others by not sharing or by being too timid to share and willing to open yourself up and put yourself out there

Depends on what your weaknesses are.

For me, pair programming has been the by far best way to improve my coding.

Test your code, every which way. Think of edge cases, security holes, do research on how code fails and internalize those lessons, and constantly test - test test test. Manual tests, unit tests, etc.

Code that works reliably is (IMHO) better than beautiful code that doesn't work at all, or falls apart under strain.

I'm still fairly green to the trade (1 yr in as a jr dev) but as best as I've been able to determine the best thing I have done is simply read a lot of code.

Reading code from people who are just getting started gives you perspective of how far you've come (hopefully). You can also use this to help people out fairly effectively.

Reading code from people who are more experienced is good for getting styles down while maintaining the context of where certain practices are acceptable.

Switching languages is also advantageous in that you can (hopefully) identify why different languages have different stylistic practices. This works better if the syntax of the language is pretty distinct - so C# to Java wouldn't be excellent at this. But C# to Python is great at it.

Of course writing code helps - especially if you can convince someone else to read that code and give some meaningful feedback!

Write a lot of code, continually rewrite it for the dumbest version of yourself.

Writing code should be highly iterative, and learning to code is as well. The more you write the better you get, and the more you will realize how crappy your old code is. Refactoring should be constant to keep the complexity and amount of code to a feasible minimum.

So, now and again, look over the code you've written, does it seem a bit overwhelming complex, are methods starting to feel bloated? Does some new feature or concept feel artificially grafted on? Then sit down and try to figure out the best way to refactor it into something more streamlined and manageable.

A good way to realize how bad one's code is to take a few weeks break from it, and if when you return you have trouble understanding the full scope of it it's time for a rewrite :)

Be sure to dedicate some time to sharpening your saw, researching new tools, technologies etc. It's easy to do something just because it's the "way I know to do it" rather than putting in the time to learn a better way.

Don't get cute. Code golf is for competitions, not production code. Even if something takes 10 lines that you can do in 1, if 10 lines is clearer, do it that way (you'll thank yourself in 6 months when you have to figure out what you were doing).

Keep your functions to one or two pages.

Keep your nesting to three levels, preferably two. If you have multiple nested if/while/for sections, break them up. (See: "cyclomatic complexity").

Follow the coding conventions of the project you're working on, even if you disagree with them, consistency is more important.

Caveat, don't be afraid to use advanced language features if doing so eliminates boilerplate.
Caveat Ruby's advances features seem to be designed to make code less readable sometimes.
Changing the interpreter's behavior is equivalent to creating a new language. The problem with Ruby (and especially rails) programs is that every program is written in a different language, and what that language is is largely defined by external imports.

I have no idea how people have the mental capacity to deal with this.

> Don't get cute. Code golf is for competitions, not production code. Even if something takes 10 lines that you can do in 1, if 10 lines is clearer, do it that way (you'll thank yourself in 6 months when you have to figure out what you were doing).

Brian Kernighan put it well: "Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?"

The other bit I know, besides this one, is to write comments stating what you're trying to achieve, and why. The how is redundant, but sample data right there may help.

(Someone famously pleaded for documenting the data structures, and leaving aside the processing mechanics.)

Clever is the enemy! Simplicity is key.
> Don't get cute

I struggle with this. I don't necessarily write a lot of one-liners, but recently I solved a document parsing problem using regex, list comprehensions, zips, etc; generally what are considered "clever" language features to my peers.

The problem-solving approach seemed straightforward enough: match labels, slice document, destruct into keys and values into separate list, transform into proper formats and eliminate errors, combine into list of tuples. I could repeat this pattern because the document was structured hierarchically.

On one hand, I believe that by using these methods I've written code that is maintainable and less sloppy, and reflects the strongest understanding of the problem. I personally find these language features empowering when I solve the problem.

On the other hand, because these features are not the simplest features, the learning curve for interpreting the code is higher. I didn't use any obfuscation methods to compress the code, but neither did I apologize for the usage of the above.

What I wonder is: if we don't try to leverage higher-level language features, do we end up defaulting to a brute-force approach with is less contained and raise technical debt?

You could hedge around this through liberal commenting, but I find it difficult to believe that avoiding thinking about the problem as being too "clever" is much better. Somewhere there must be a compromise.

Rule of thumb: Any language feature or library API that is clearly and unambiguously documented, not obsolescent or discouraged, and legitimately the best tool for the job, is fair game.
You have to be clever sometimes, take strlen in C you could do byte at a time which a compiler will vectorize or you could do SWAR with some fancy bit hacks and get a large perf boost.
A better statement might be: "Don't be clever, let others be clever for you."

Obviously, stdlib functions are going to be highly optimized and the compiler will put hardware-specific performance hacks in for your benefit. Smarter people than you wrote, vetted, and approved that code. Use it, and don't ever try to be that smart yourself.

> Keep your functions to one or two pages.

When I encounter functions that large, where the body isn't almost entirely declarative, they tend to be doing way too much. A function longer than 10 lines is a smell, IMO.

I love Haskell but I feel it attracts the most golfy code. E.g. Yes you apply the dot operator to itself but why would you?
> I love Haskell but I feel it attracts the most golfy code.

I don't feel like most Haskell code is like that and I've read a fair amount.

> E.g. Yes you apply the dot operator to itself but why would you?

I write a fair amount of Haskell and I despise seeing code that applies the dot operator to itself.

Another lambda rather than a double compose is almost always the answer.

> Another lambda rather than a double compose is almost always the answer.

Exactly. I also like to choose between do, >>= and <$>, <*> etc. based on readability. I prefer a slightly more clumsy but readable do {...} in many situations.

After 25 years of programming I'll give you the lesson my first boss gave me: "Think more, type less".

It's stood the test of time, I've passed it on to many juniors.

"Weeks of programming can save hours of planning" is a favorite quote of mine.
> "Weeks of programming can save hours of planning" is a favorite quote of mine.

I love this.

My two favorite generalizations are: - Slow down to speed up - Less is more

another variation of "slow down to speed up" is "slow is smooth / smooth is fast"
While this is true, the opposite can also be true. That's called "Analysis Paralysis".
Most of the time analysis paralysis is a signal that you lack sufficient understanding of the needs to actually write the right code. There are two ways to break out of it: (1) understand the problem better, and (2) build something, anything, see why it wouldn't work, and then go back to the drawing board.
> When something went wrong, I'd reflexively start to dig in to the problem, examining stack traces, sticking in print statements, invoking a debugger, and so on. But Ken would just stand and think, ignoring me and the code we'd just written. After a while I noticed a pattern: Ken would often understand the problem before I would, and would suddenly announce, "I know what's wrong." He was usually correct. I realized that Ken was building a mental model of the code and when something broke it was an error in the model. By thinking about how that problem could happen, he'd intuit where the model was wrong or where our code must not be satisfying the model.

http://www.informit.com/articles/article.aspx?p=1941206

I've found the opposite. Maybe "Ken" could do it, but most programmers who think they can can't. I've had three programmers spend three days thinking about a problem to no avail, and then fired up a debugger and tracked it down in half an hour. Never be too proud to use your tools.
This was my first thought, as well. It's hard to think when you're staring at the blinking cursor. Spending a little bit of time with a pen and a napkin works wonders for design and reduces the number of times you'll have to hack in fixes for cases you didn't consider.
> "Think more, type less"

I agree. While still learning to code, I discovered this aphorism myself when the thought dawned on me that contrary to my previous practice, the programmer thinks for the computer and not the the other way round.

It may seem banal but it has helped me a lot.

Interesting. That's the opposite of my experience. When I try to think about things I get them wrong. When I just write code it works out.
The number one thing you can do to write better code is get together a group of people and do code reviews on each other's code constantly. Have people in the group point out things they like and dislike about the code, along with why. Then create a "cheat sheet" where you try to summarize the principles behind all the suggestions people make for your code.

Beyond that, try to find "good" code look at how it is structured. Then try to emulate the practices in regards to language feature use, layout, etc.

Work it work best in a work setting or a friends settings?
It can work anywhere. If your work isn't doing peer code review, they should be - I read somewhere that code reviews are the single biggest driver of high quality software. It is also easy to start up a code review session at hackathons.
Imagine you will have to go back to the code and work with it, after not having spent a single thought on the project for 12 months.
...and possibly during those 12 months have 12 people try to murder your code, i.e. expand its functionality. If the overall architecture, layout is clear, there is a chance that they will expand, improve your code in the way that you originally intended. If it is messy and unclear, it will get more messy. (I'm currently doing a re-write of a huge project that grew over time, using these principles, trying to learn from what was not so nice in the old codebase)
Write cross-platform code
My main two:

1) "Overview and Zoom" is a common UI technique where you give an overview and then let a user click on stuff for more detail. In programming, this means writing the "intention" as a method that's very easy to read, then have a bunch of helper functions with the details. This also forces you to only use one level of abstraction per method.

2) Only have one thing happen per line. This forces you to give what you're doing a proper name (as a variable assignment before you use it). This makes it easier to read, but makes all text processing tools work much better with your code. Nothing is more annoying than seeing a diff in a pull request where one line changes, but there are 10 things happening in that line and only one changed.

Study programming languages in different paradigms than you're used to
Good code isn't obvious that it is good. Bad code is obvious that it is bad. (Its like TV presenters - you notice a bad one, but good ones are the norm, so it just seems "average").

Get a job maintenance programming on someone else's crappy code. Every time you think "WFT!", ask what would have made it easier.

Likewise, if you are working on a long term project that you have written, then a month or two later revisiting the code you should be able to understand the code you wrote quickly. If you don't understand your own code straight away, ask yourself what it would have looked like to make it easier to understand. Refactor it and try to improve it.

Less is more -- Keep the lines of code down to a minimum in a single function/method. Its name should say what it does and it should only do that one thing. Also, you should always look for code to remove/refactor to minimize its function to singular actions.

Avoid complex statements or crazy one liners when it would be better understood by 80% of developers if you just put it in a simple if statement or broke it up into 2-3 lines. Break up algorithms this way with good comments too.

Experiment with small pieces of code before committing to a specific design if you are unsure. I have quite a bit of experience and will still write small test programs all the time to test out an idea before I try it in my main code base. This lets me make sure I got my idea (and implementation) correct in the simplest form before trying to put it into a larger more complex codebase.

Don't start optimizing in places you don't know you have an issue. Write it out like you want first, then optimize later when you find it is an issue.

Don't try to "future proof" your code, no one can predict the future and writing extra code to handle what might happen is basically torture, both to yourself and the person that has to remove it all later cause your assumptions were wrong. Not to mention, this is where I find the most errors in code.

Learn data structures well, and use them properly. It drives me nuts to see how badly people abuse data structures and misunderstand their benefits and drawbacks. But see my last point too.

My personal pet peeve, don't assume the person before you did it wrong or was an idiot until you can prove it. Generally devs do something, even if it is not ideal, for a reason. Be it, business changing requirements at the last minute, or schedule crunch or a lack of domain knowledge. But before you call them an idiot or assume they were wrong, try to figure out what they did and understand why they might have done it. I have seen a lot of code get changed by new comers only to find out it was written that way for a specific use case that they just didn't understand yet. Then shit breaks, people get upset and the whole team gets frustrated.

I agree with you on the last point. Humility and assuming best intentions works very well in all settings, but especially in legacy code. I withhold judgement on everything I see now. It took a bit of time and a few instances of eating crow to get there.
Yea, totally ate my share too. For me, I also had an interesting lesson where I returned to a company like 10 years after having been there prior. I picked up some code to fix a defect and was frustrated by it, only to realize I was the "moron" that wrote it prior.
I generally agree with this comment, but wanted to pull out one nugget in particular:

> don't assume the person before you did it wrong or was an idiot until you can prove it.

Heh, I don't want to count the number of times I've looked at a line of code and snarled about how stupid it was... just to git-blame and find out that I wrote it. Nothing will break you of the habit faster than maintaining your own code.

> when it would be better understood by 80% of developers

And yourself, 6 months down the line.

It depends on your experience. When you're starting out you want to write as much and as often as you can. Practice solving the same problems using different solutions and adding constraints.

If you're an intermediate developer you need to turn to predicate calculus, logic, and formal mathematics. Learn a specification language and a theorem prover. Learn to think above the code. Learn to identify what the invariants are and then see what you can cut away from your code. Know what pre-and-post conditions must be held strongly and which can be relaxed. Etc.

Learn how to type well (>30wpm). Literally everything else is secondary. You don't have to be the fastest or most accurate typist, but not having to look at the keyboard while typing pretty quickly and accurately is foundational.

Not having to use the mouse for common tasks (closing a window, switching applications, etc...) is another core skill.

Everybody else's comments focus on important stuff but I've seen programmers absolutely hobbled by not having these basic skills.

Write a "how this piece of incredibly complicated software works" document for a piece of software you have no idea of the workings.

I have never actually published any, but have done this several times. The process is unlike any other form of writing; you are constantly rewriting all parts to reflect your incremental gains in understanding.

P.S. I believe there is an inversely proportional documentation to complexity rule, where at some point the adequate amount of documentation to describe the complexities becomes unmaintainable. In this case, the code becomes a better descriptor of the code than any amount of documentation ever could.

Examples I've worked with include the go compiler and Scala's slick library.

Go back to code you wrote 5 years ago or even 3 years ago and refactor it. You will most likely cringe but it will force you to write it better.

Also - write code that would be easy to understand for a jr developer. I see so many developers trying to be creative with their code but it's hard to understand.

Go read open source libraries. I started out barely knowing anything in Javascript and I had terrible code, as almost any dev could say. However, I was never afraid to follow the debugger through the code of the libraries I used. At the time these were things like Backbone and Marionette, which have incredibly clean and well documented code.

Find a library you use and have a feel for how it works, and read through their code. If you run into questions about why something is done a certain way, they'll tell you! Use git blame and see what the commit message was for those lines! Often there will be a description or issue tied to it and you can figure out the thought process they used. Then just start applying similar thinking to problems you encounter.

This. Just familiarizing yourself with developers of popular/widely-used codebases helps immensely. Make sure to stray outside of your peer group for this, too.
Any suggestions on how to get outside of your peer group?
If you're a WordPress developer, look at some non-WordPress projects.

If you use Angular all the time, check out some React projects.

Use Bootstrap as a starting point for websites? Check out Zurb or any of the new CSS frameworks that are posted here every week.

You don't have to switch... Just become aware of what others are doing.

In addition to the other suggestions, take a personal interest in technologies you may not be able to use professionally. If you write Java at work, maybe go learn Erlang. If you build web pages, go learn database optimization. If you do anything other than security, learn how exploits are discovered.
> Go read open source libraries.

That can be good, but some caution is required I believe. Most developers are writing programs to solve a fairly specific problem operating in a fairly specific domain in a fairly specific environment. They may choose a variety of programming styles to approach the problem (imperative, object oriented, functional, and so on). I'm going to call these kind of programs "normal" program.

Libraries are not normal programs. They are tools or components that are used by those writing normal programs. A good library tries to be accommodating to a wide variety of normal program styles, and tries not to impose too many constraints on how the library is used.

To achieve this, library coders often have to do things that would be poor style or poor design if done in a normal program.

Deliver real, end-to-end working functionality, at least every two weeks and ideally more frequently. Unless and until the code is used it's all meaningless. This more than anything will protect you from overengineering or building the wrong thing, which are the biggest dangers.

Delay design decisions as late as possible. Unless and until you need to make a decision to allow you to deliver some real functionality, don't make it. Avoid any planning of things like code structure. This will give you the chance to make better decisions, since you'll have more knowledge at the point when you make them.

Reduce the number of lines of code. People will say that other things are more important, that making the code longer can sometimes make it more readable. That's an intuitive view, but in my experience it's wrong. Reduce the number of lines of code, and everything else - good design, good code structure, good language choice - will follow.

This is the most important thing: "Unless and until the code is used it's all meaningless."

The risk of writing perfect unused code is significant, and the consequences are pretty grave. I think worrying about how "good" your code is should be secondary to worrying how usable your code is.

Yes, maintenance & debugging can really suck. But optimizing to make maintenance & debugging better won't serve your users well if your code never launches.

Not writing code until it was _absolutely_ needed would have saved literally months of my working life, and I haven't been working all that long. Business requirements change constantly, so what you think will be needed probably won't be by the time you ship the code.

Continuously refactor your code to remove duplication (See Don't Repeat Yourself / DRY). If you find yourself copying and pasting boilerplate code over and over, it's probably time to refactor, otherwise you find one bug and have to fix it 20 times. Good testing helps you to refactor fearlessly because the tests will tell you if you screw up.

Whatever happens you'll always cringe reading old code - but see the positive, it just shows that learning is a continuous process and that you are now older and hopefully wiser than you once were.

One thing that's been on my mind lately is an old article by Joel Spolsky [1] about accurate estimating. I'm absolutely terrible at estimating but I'm using Joel's method to improve that. Better estimates mean better code. Why? Because if you estimate 1 day for a task that really takes 2 days, you'll be rushing, stressed and tempted to cut corners by the start of day 2. If you've planned 2 days for it, you can use the time more effectively.

[1] http://www.joelonsoftware.com/items/2007/10/26.html

> One thing that's been on my mind lately is an old article by Joel Spolsky [1] about accurate estimating. I'm absolutely terrible at estimating but I'm using Joel's method to improve that. Better estimates mean better code. Why? Because if you estimate 1 day for a task that really takes 2 days, you'll be rushing, stressed and tempted to cut corners by the start of day 2. If you've planned 2 days for it, you can use the time more effectively.

Only if you were using that planning for something. Is the business value of that feature really so marginal that you want to do it if it takes 2 days but not if it takes 1? I find it's more useful to prioritize, limit work in progress, set a cap on how long any one task is allowed to take before you reassess, but explicit estimation isn't worth it.

Always think of the next developer who will maintain your code