Ask HN: How do I learn to write better code?
Some context for this question: I'm a mechanical engineer by training and I now work in robotics; developing path planning models in a research setting. I didn't have a traditional education in Computer Science. When I look at the computer engineering posts and comments on HN/Reddit, I feel woefully inadequate. I've never used these technologies like Kubernetes, Docker etc and feel out of place.
So, how do I write better code? How do I plug my CS foundation to better suit the current scenario of engineering?
60 comments
[ 3.1 ms ] story [ 121 ms ] threadBesides that, when I come across a technical article - or even comments here on HN - I print it out to take it offline to read later, highlight, mark up. Then months later I circle back to it and usually find that I'm a lot more familiar and knowledgeable about it than the first time - a kind of long term spaced repetition.
Reading other people's code is also really good. Do you have other engineers in your organization that you can read code from? If you don't, try to find open source projects in your field. Again, I like to print things out. It is the best way I digest something. So yes, I print out code to go over later when I'm having dinner with wine.
If you want to dive deeper in to Computer Science and become a Software Engineer, then there's a lot more to learn. Here's some short tips:
- Learn a few programming language for their paradism: Java (OOP); Python, Hashkell, Scala (Functional Programming), Rust (memory safety). You don't have to actually work on them, just need to be able to write some algorithm and know their key features.
- Read more code, especially those from high quality projects.
- Experiment a lot. Don't just read articles, blog posts. You have to get your hand dirty.
[0]: https://www.amazon.com/Clean-Code-Handbook-Software-Craftsma...
Personally, I like to go back in the git history to when things were simpler and see the changes to the codebase that solved specific problems they had, e.g; scaling, reusability...
I often find the best service someone experienced can render to someone unexperienced is the service of curation, i.e. hand pick from the vast amount of available options a few that are likely to help.
For example I read the source code to `less`, and the manual. Despite "knowing" less I learned a lot from reading the manpage and reading the code.
Same with GNU Make. I could say I knew it before I started, in the sense that I'd written simple Makefiles and knew how to run `make -n` , `make -j4`, etc. But reading the manual from start to finish, and reading the code meant that my knowledge was more complete.
Some programs have terrible source code (e.g. "telnet"), and others good (e.g. "redis"). I think that a list of examples might be useful, but more useful still would be for people to choose things they know, and they use.
Reading all about the design of postgres might be wonderful, but if you're not actively using it then the details aren't going to be so memorable.
Better yet, because I read the source to programs I used I was even able to contribute bugfixes to them. (In the case of less/make at least. Not always.)
Ideally, you’d find a mentor who can review your code and give you pointers on how to improve it, with explanations on why those things would help. Those mentors aren’t easy to find either, but still...
The first, and IMHO most important component of your question, is how to improve your actual written code. A few ideas:
~ Pair programming with an experienced programmer, if possible. If not, try to work on a project (even an open source one) with someone who is an experienced software developer and who does comprehensive code reviews. Code reviews from articulate, experienced developers are an excellent way to learn.
~ Write lots of code. Write code in new areas unrelated to your job. Try to find some small project or projects to do. Once you're done, reflect on what you've done and what you could do better next time.
~ Read books like Clean Code. It might also be wise to pick up a book on design patterns, particularly if you'll be working much in object-oriented languages like Java, C#, or C++.
~ Always read source code when possible. Using a new tool at work? If it's open source, go check out the code. Starting a new project? Go find similar projects on Github and browse the code. You could also find some well-known open source projects and read through their code. A good book for ideas on which open source projects to focus on is called The Architecure of Open Source Projects (there are two volumes, shown at the bottom of this page: http://aosabook.org/en/index.html)
~ Learn other programming paradigms, such as functional programming (maybe learn Haskell, or Scheme) or logic programming (learn Prolog). You may never use such languages in your work, but languages in other paradigms will provide you with both a better understanding of computation, and a bag full of useful techniques and ideas for your working language(s).
The next component is how to improve your understanding of data structures, algorithms, and other topics rooted in theoretical computer science.
~ Consider doing problems from sites like leetcode.com and hackerrank.com.
~ The book _Algorithms_ by Robert Sedgewick and Kevin Wayne is very good, as is their free two-part course algorithms and data structures offered on Coursera (https://www.coursera.org/learn/algorithms-part1).
~ Work all the way through the online book Structure and Interpretation of Computer Programs (https://mitpress.mit.edu/sites/default/files/sicp/full-text/...). It will not only touch on a number of important theoretical computer science topics, but you'll also get some good exposure to functional programming.
Finally, to learn commonly used technologies you see on HN, such as Docker or Kubernetes, you'll just need to get your hands dirty:
~ Rent a $5 server on Digital Ocean, or set up a home server, running a Linux distro like Debian or Ubuntu (which often makes installation easier), and start experimenting with them.
~ Continue following HN and reading about new technologies and programming languages.
~ Realize that it's critical for software developers to constantly learn new technologies. Otherwise, they very often become unemployable after a decade or two. HOWEVER, it's also critical that devs exercise restraint and good judgment when selecting technologies for important projects. For such projects, it's usually better to choose languages and technologies that have a proven record and that you personally have a good understanding of, over some new technology that's getting a lot of attention on HN.
With respect to all three of these components, it's a very good idea to maintain a blog and write about your experiences and projects. A blog will serve to not only consolidate your new knowledge, but also to improve your writing and help ...
Take a look at the Clean Code book. The chapters on functions and naming are some of the best.
You can find yourself a tutorial on these, go though it, and have a sense on what you can use them for.
For example, we use Docker images to spin up Jupyter notebook servers with the most common dependencies for our users, plus other components that add features like notebook scheduling, and live collaboration.
It would be hard for users to set that up thelselves. Think of it as a way to package and deliver value without the fuss.
How to write better code:
- Read good code: clone repositories of proven software that you use, and read from it from time to time.
- Curiosity: when you use software, you may encounter functionality and wonder about it. If you have the code for that software, you can look how it was implemented and architected. Doing a git blame and finding the first commit where something was introduced is educational. You can then advance commit by commit to see how it played out.
- Vocabulary: sometimes you don't know something and you can't even look it up because you don't know the word for it. For example, there are concepts like 'message passing' or 'multiple dispatch' that I have pondered on for quite some time before knowing what they were called. I was trying to formulate a search query to find what that way of coding was. The same thing happened to me when I wanted to learn how to think better and improve my cognition. I had tormented myself thinking 'is there a field from which I can learn to think about my own thinking. Cognition on my cognition'. I felt stupid when I stumbled upon it and found it was simply called 'metacognition'. Sometimes I was luckier. For instance, after observing how some software can add new functionality from pieces created by third parties, I knew I wanted to learn how to do that. When I was a child, I found it really cool that you could make a Winamp skin, and Winamp would transform to that skin even though the Winamp folks ignore everything about your existence. They didn't need to integrate your skin. I found that to be really powerful. I had the docs for win32api, and found it amazing I could use it to access system functionality, etc. When I wanted to transform a Doc to PDF, I installed an extension and Microsoft Word knew where to display it and what to do with it. Same with applications. The underlying themes are APIs, extensibility. The materializations are plugins, extensions and applications. The supporting principles are good abstractions, and open/closed principle, dynamic loading, and interfaces.
It helps to structure the thought into 'themes'. Extensibility theme, for example. And then categories, like a plugin architecture. Keep those and link new things you learn to those themes. These compound and find their way in your new software.
- Write the documentation or the use guide first. A description of how your yet to exist software is used. You write 'X does this, here is how you use it'. If everything were possible, what would the usage be ? How would a user interact with it. Write it, then code the simplest version that does the job.
- Don't be unproductively hard on yourself. Pick any amazingly successful software and do a 'git log --reverse' to see the first commits on top. Ad augusta per angusta.
You might try to find some prerecorded sessions in your field.
That relationship can be something simple. You can bounce design ideas by them, ask them to glance over your PR before you open it to the team, or ask them general workplace questions.
You’d be surprised at how willing people are to take you under their wing. It’s ingrained in us, and I find that the better engineers are more likely to accept these requests than the bad ones.
Look into problems, you'll find solutions. Look into solutions, you'll find problems.
There are some practices that make your app easier to manage, but you need to understand in which circumstances you need to use them.
A lot of the successful companies that are out there, got there from running a simple Django/RoR monolith until they were 60+ engineers.
Pick any one stack, build some simple projects and then expand on it. You would also want to be able to read well written code from others on github and learn from those.
If something fashionable looks appealing, I would say, "drill down and understand the core of what pain point it's addressing". If you are convinced, check out "how you would have done it otherwise", and if you are happy with the fashionable thing, add it to your "fashionable thing" budget. Keep that budget tight. Be ready to revert at any time.
God, I love this line.
Our industry, especially these days, is flooded with "solutions" that promise much - and the best ones do deliver; but what's suitable in an enterprise environment can be a huge mismatch and complexity cost for smaller companies, individuals, or for research purposes.
I heartily agree with the grandparent post, to focus on what the problem domain requires, to use tools and dependencies that specifically support that goal.
Too often we see a framework, library, language or tool used for the sole reason that it's what the big players use - basically driven by popularity and inertia. There are advantages to buying into an ecosystem or particular scalable approaches, but only if it suits and serves your purpose.
As for writing better code - as many comments point out, the best way is to read and write lots of code, study books, follow the best works of people in the field.
It helps to start with the specifics, I think, by having (or creating) a problem to solve, or a project with clear goals. One can start with the simplest, "naive" approach, to solve the immediate needs - often that involves finding off-the-shelf libraries. Then, consider ways of improving the logic, refactoring, maybe dig deeper into the libraries to understand how it's organized.
One place to start might be learning to write good unit tests. This would require:
1) learning the basics of how to use a modern, nice unit testing framework for whatever language you use.
2) learning the skill of how to come up with good test cases for your code.
3) learning how to write code such that unit testing is easy. this will also make your code higher quality and force you to learn about design and software engineering, but in a way that will actually be useful immediately. Books like highly-recommended "Clean Code" are relevant here.
Software Engineering in essence is about taming complexity so that mere mortal humans can build cost-effective complex software, when you reach to million lines of code, you wouldn’t want complexity get in the way. So I recommend you follow these guidelines:
* Build your software as if it is a house where you can add more rooms as it grows. Ensure that software architecture is modular, each module should be capable of working independently from each other. You will further atomise modules through classes. So basically composition goes like this: Software -> Module -> Classes -> Methods. You absolutely need to get modules right. Mistakes made at class level can be easily corrected but it is harder to separate module level entanglement.
* Variable/function names should be self explanatory. Don’t name them too short or too long, they shouldn’t confuse/waste the time of other people working on the project.
* Code clarity is extremely important. Don’t write hard to understand code unless it is an absolute necessity. Premature optimisation is bad but you can do premature optimisation so long as it doesn’t affect code clarity. Otherwise leave optimising code to last stage.
* DevOp tools like docker incredibly increase the production speeds and overall software quality. So try to keep up with these but don’t go 100% on them as they might also increase the complexity. Gradually add devops tools to your project as it grows. You probably don’t need to implement every single DevOps tools. For example, if you got less than 20 production servers, you probably don’t need kubernetes.
These are less about coding, more about ops. I'd only bother reaching for these if you know you need them.
> So, how do I write better code? How do I plug my CS foundation to better suit the current scenario of engineering?
Start with a project. What is a problem you face daily or weekly that you'd like to automate to some extent? What is a problem your friends or family have that you think would be interesting to solve?
Find a project, break it down into its pieces, and start coding. Your code is going to suck. You're going to look at it a month later and cringe. You'll look at it a year later and vomit in your mouth a little. But this is good, it means you're getting better.
As far as learning, I'm not much of a reader. I never learned well by looking at other projects unless I was picking apart a very specific feature. Instead, I learn by doing: trial and error. I found this methodology works great with languages that have a REPL (javascript, python, lisp), but you may want to focus on a language that's more specific to the problems you will want to be solving.
No matter how you learn, once you've been programming for a while you'll start to built a vault of patterns you can use to solve specific or general problems. You'll also find that once you get good at one language, switching to another is much easier than learning the first because a lot of the knowledge transfers.
Hope this helps, good luck!
I'm an ME but have been mostly doing SW for a very long time. What I remember helping, in addition to the advice here:
1) Start small, don't dive into a complex project right away but instead find the interesting parts of the project where your domain knowledge is useful. Rely on seasoned SW folks to maintain the framework and then slowly learn inside out.
2) Use the debugger! In addition to reading the code and any documentation on the architecture the debugger can help reveal the often crazy runtime behavior. At first just step through your code and watch what happens, then look at the call stack and, if applicable, threads or other concurrent stuff going on.
https://www.destroyallsoftware.com/screencasts
My guiding principles when recording my (now over 25) videos are:
- prefer timeless technologies (e.g. unix, testing, C) over fad frameworks
— focus on trade-offs — every idea in CS has a cost
— don't be afraid to peel off one layer of the stack
— set everything in real production code rather than in toy examples
- emphasize actual workflows (showcasing vim and bash and all the associated keyboard shortcuts)
https://www.semicolonandsons.com/
2. If you do multi threading: Look into Immutability and communicating sequential processes
3. Use static analysis / linters / code checkers for every language you use. They provide a lot of tips. There are linters for bash and Dockerfiles as well.
Don't get hung on technologies for technologies sake. Kubernetes, Docket are just tools that are useful in some contexts and are completely irrelevant in others. The fact that a significant portion of HN is engaged in modern web backend development doesn't mean it's the only significant area of engineering.
> So, how do I write better code?
Short answer: be lazy and hate work.
Fixing bugs is work. Instead of just fixing it, find out how you could've used the type system or API design to prevent yourself from making this bug in the first place. Fixing this bug is work. Preventing yourself from making the bug in the first place is saving yourself of doing the work.
Rewriting your old code when you get new requirements is work — learn how to build an app architecture that would make this amount of work minimal.
Writing code is work. Maintaining code is work. If you manage to complete your tasks using less code, you'll have less work.
Manual testing is work. Writing unit tests is saving your time. Learning how to write tests so you won't have to rewrite them when your requirements change reduces the amount of work you have to do even more. And leveraging the type system to maintain some invariants about your code saves you even more work.
Communicating with your colleagues and answering their questions is work. So, learn how to write code that is easy to understand. Writing comments is work too, so if you manage to write a code that is so clear that it doesn't need comments, you'll save yourself from even more work. Writing great documentation will save you time on work chats.
Of course a big difference between being lazy and being irresponsible. We should always thrive to achieve the best possible result for our customers, internal or external. But actively trying spend less effort without compromising on quality is the best driver for innovation and learning, in my experience.
If getting a job doing it isn't a viable proposition for your life goals, then alternatively you could study well-written OSS projects.
Most of that gap has to do with the complexities of deploying, maintaining and scaling a system in production. This recent blog post does a fantastic job at providing a taste of these complexities:
https://rachelbythebay.com/w/2020/09/20/evolve/
It’s these complications that make up most of the gap between what’s in the textbooks and what’s in the real world.
Kubernetes and Docket don’t make your code more efficient, more readable, more terrible, more correct or more stable. They solve specific problems that teams encounter in one part of the software industry. Engineers writing device drivers at Apple will have a different set of tools, practices and coding standards for example - if they optimize for running tests in the factory and reducing the drain on the battery, it will be a different set of complications that will create the gap between textbook code and what’s deployed. Apple’s production quality code will look extremely different from Facebook’s because the complexities are different.
There is a small part of the gap between textbook and real world code that is not caused by domain specific nuances. It’s pretty easy to pick up those skills:
1. Use a code formatter like clang-format and/or a linter. Pick a coding style and stay consistent with it.
2. Write code to communicate intent. Name your variables and your methods so that the resulting code reads as close to prose as possible. Write your code in a way that someone you’ve never spoken to can easily figure out what’s going on and modify your code.
3. Design your APIs first. Think long and hard about your API, how it will be used, how it can be tested against, how it can be extended in the future. Read “The design of every day things” by Don Norman and pick up on what is good design.
4. Related to 3, design your API (and implement) them in a way that’ll allow as much of your code to be unit tested as possible. Have an algorithm that will run when your robot hits a wall? Pull your algorithm into a separate file, with its own API in such a way that you can compile that algorithm into a second “test” program that’ll feed it some inputs and check if it produced the expected output. Use a code coverage tool (google gcov) to see how much of your code is covered by unit tests. Aim for 98% or higher. The “should I move these lines of code to a new function” is then easily answered with “will a new function (in maybe a new file) help me increase my code coverage in unit tests?”
5. Write your code so that tiny errors blow up into catastrophic failures as quickly and as loudly as possible. Add error messages everywhere. Add asserts and static_assets everywhere. You are going to spend a lot of time tweaking and testing your code - if any change triggers a failure anywhere else - you want it to fail with a really really loud bang so that you are more likely to notice (and debug) it.
6. Avoid creating skews/variants in your code. don’t have a debug version and a release version - write code that you can test, deploy code that you have tested (exactly the same code, with no changes)
This will get you over that gap between text books and real world. The rest you will figure out as you go deeper into your domain.
How do you focus on the product? Testing, especially testing of the end product behavior and performance. Alternatively and if applicable, learn formal methods such as TLA+.
Also, read other peoples code, and for whatever language you choose, find the aggregators, reddit, twitter and start reading them. Eventually you'll find someone who write very clear code, and start following them.