1) Use the best language you can get away with. This means using a language that eliminates entire classes of run-time errors via some or all of:
a) Strong typing
b) Static typing
c) Managed memory
d) Immutable data
2) Assume that your code will be maintained by somebody who doesn't know your programming language as well as you do.3) Use static and dynamic assertions liberally.
4) Ease debugging. Have great formatted dumps of your internal data structures, great internal consistency checking, and great logging. Automate problem isolation, too: for example, in an optimizing compiler, at each point where a discretionary change is about to be made, call a function that logs the change, increments a counter, and returns false if the counter is past a limit that you can set in the environment; this allows an automated bisection analysis to immediately find the change that broke a test program.
5) Regression tests. Once you fix a bug, make sure that your test suite will notice recrudescence.
6) Don't hire or retain sloppy programmers.
Also, your first three points puts Ruby pretty much out of the picture.
EDIT: Mentioned Ruby because that's what I am working on right now, after Java and Scala
That someone is worried about being sloppy really only lets you assume they don't write code they consider sloppy.
Of course, that will actually mean they move much more slowly due to bug fixes, but a junior dev in an environment without a good senior dev can easily fall to the "quick and dirty" fallacy.
And for 4) - don't be afraid to build instrumentation into the code base to support decisions about correct operation.
One of the most important things I learned from chess was to go back over your previous games and look for what mistakes you made, why you made them, and how you can avoid them next time.
Do this same analysis with every BUG you see a bug in your code(whether you wrote it or not). Ask yourself why you or someone else make this mistake? What is because they a assumed a method did something it didn't? Was it because the domain logic wasn't in a single easy to find place so they reinvted the wheel and did it incorrectly?
It's stood the test of time, I've passed it on to many juniors.
I love this.
My two favorite generalizations are: - Slow down to speed up - Less is more
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.
1) Always write code for someone else. And you don't know who it is.
2) Write tests not only to prove correctness, but also to test whether code as an interface makes sense.
3) Be defensive as much as possible. Each additional check / assertion helps to avoid accidental bugs or can help to find them early.
4) Speak about code as much as possible. If you can explain code in your own words and discuss it with someone else, code gets raised on a philosophical level and may end up to be elegant and logical for others.
5) RTFM & RTFS(pecs)!
6) Read lots of code. The more of other people's code that you read the faster you'll learn good practices.
7) Work with other people that are better/more experienced than you.
8) Code reviews are your friend. When I was at Google, the best way to learn and teach good coding practices and libraries were through the code reviews that we had. Get in the practice of reviewing other people's code and having others review your code.
9) Test, test, test! It may sound counter intuitive, but if you want to increase coding velocity, write tests. Tests upfront will save so much time in debugging later on.
How do you get to read "good code" outside of the job? Care to point me to some such java resources.
> How do you get to read "good code" outside of the job?
You don't have to read just good code to learn good practices (negative examples are important, too), and there's lots of published code readily available (e.g., open source repositories.)
Though, I'd say you need to work with lots of code to learn good practices; merely reading code may give you misleading ideas of good vs. bad practices.
Also, always put a breakpoint on every code branch and step through it at least once in the debugger. Everyone makes mistakes. Fixing mistakes at the point of writing the code is much more efficient than having someone else fix them later. If you haven't done at least this level of testing, just don't check it in. Assume code is broken unless it has been tested.
2) You can't prove correctness with tests. You can only state that it ran correctly for that test case(s).
But you are right, tests are biased. Pair programming can reduce the chance of code/tests being biased in a negative way.
For example: int addOne(int input) function would need test cases for every number in the int type, which would be 2,147,483,647 * 2 + 1 test case.
IE, a test is another way to explain what the developer of that test cared about, worried about, needed to verify of the code that is exercised.
Also, tests provide anothing built in consumer of the code, and all code with more consumers is better code, purely by surviving the stress.
Good test vectors are a great investment, as are constraint checks and supporting instrumentation of said constraint checks.
For example proof assistants(like Coq, Agda, Idris, hol) are capable of proving correctness of a program.
I think random testing is a good way to get almost exhaustively tests.
> I think random testing is a good way to get almost exhaustively tests.
I think that testing known/predictable edge and corner cases makes more sense (in some cases), but I also think that I was responding to a comment about the impossibility of using tests to prove correctness of a function, not about how to derive practical benefits despite theoretical limitations.
You are right, you can use tests to prove a function if - the function has very limited arguments and - the function is pure or does very limited amount of state change(side effect)
Generally speaking tests are not the right tool for proving program correctness.
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.
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.
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.
Maybe starting with a project like this would help? https://github.com/aosabook/500lines
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.
I have no idea how people have the mental capacity to deal with this.
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?"
(Someone famously pleaded for documenting the data structures, and leaving aside the processing mechanics.)
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.
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.
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 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.
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.
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
1/ When given the choice between making something exist and making it not exist, lean toward the latter. ie. Minimize Everything. Remove everything that can be. Things must earn their right to exist.
2/ Choose the best names you can come up with. If you can't find a good name for something, there is a chance it shouldn't exist. There are books about this.
3/ Code should enable things, not be a problem itself. Make it the easiest to maintain and understand above all other criterion. Code like you have an audience that will be reading that code while not really caring regardless. You can't expect the maintainer to care as much as the writer, it would be impolite to do so.
4/ Think a lot about what things should be splitted in two concepts, and what concepts should be merged into one. Iterate designs until they seem impossible to enhance. If it feels wrong it is probably wrong.
5/ Recognize your weak parts and improve them, not only those that are good already.
6/ Don't confuse input errors and bugs.
7/ Create the right abstractions if necessary and at the right level. Lean toward "no abstraction" if you're unsure of the best design yet.
8/ Forget everything systematically. If you need to remember something, find a way not to have to. You need all your neurons to learn new things constantly.
9/ For the hasty: work on legacy systems. This is the "hardcore difficulty" level of programming. Do some parsers.
10/ Don't give a name and reify everything, in fear it would become a topic of mental activity. Example: since users will report the biggest problems the most, maybe there is no need to record every last user wish. If a task is faster than to record it in the todo-list, make it right away, etc.
This was definitely the most influential experience of my software career. Being one of 5 devs that maintained a 5 million LOC montrosity was a hell of an education.
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.
> 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.
And yourself, 6 months down the line.
First question to ask is always: Do I need to write some code or I can solve this problem using tools already here?
If you decide to write code, then start to think really hard.
What interface should I provide? The smallest the interface the better; and be aware that any kind of configuration your interface use increase exponentially the size of the interface itself.
Now you should experiment, take your time and be sure that the interface you are providing is the smallest possible, try to see the problem from another point of view, don't be afraid to throw away code (don't write test yet if not for experimental purpose, you don't even have the interface to test) and try to keep everything stateless and pure.
During this process is important to gather feedback from your peers, they may see problem that you don't.
Once you have decided what you are going to code, write it down.
Try to keep everything so simple that test will seems pointless, write those anyway.
Be slow, take your time for thinking, don't be afraid to throw code away.
This is really good advice. Also think really hard about what is the real problem you are trying to solve and only write code to solve that problem.
The oxymoron is the best code you write is the code that you did not write.
How do you know you have thought hard enough?
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.
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.
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.
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.
Read this book: https://www.amazon.com/Debugging-Indispensable-Software-Hard...
It's short and sweet and applies to any technology problem.
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.
[0]: https://steve-yegge.blogspot.com/2008/09/programmings-dirtie...
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!
There are lots of projects that are good examples.
Most of the lessons about programming I learned by designing and coding sets of features, shipping them, and then being the one handling all the bug reports and feature requests. Performance tuning, defensive coding, debugging technique, ui design, codebase architecture, developer team coordination, ... the list of things to be learned by sticking around for the long haul is endless and hard to replace with book smarts.
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.
Put the effort in to learning new concepts, programming languages, methodologies, programming language history, computer science, and even broader subjects dealing with humans and how we think and form ideas. There are countless ways to write code and without having a broad knowledge of them you will only at best reach a local optimum and rarely choose the most effective solutions and designs. The worst thing you can do is stop learning and seeing other perspectives.
* Software development methodologies, things like Mythical Man Month
* Computer Science history and basic theories and concepts
* Compiler implementation techniques.
* Anthropology and related subjects. Programs are written by people and for people so understanding people helps a lot.
The biggest thing is to approach things from all perspectives and an open mind. Learning programming languages of different types is great because the different language families highlight different techniques, concepts, and ways of thinking. No one approach or language is the perfect solution to everything. Try to aim for the largest perspective: functional, homioconic, machine like, pure OO, strict, lose, etc. You can look at the lineage of programming languages and what developed and derived from what and learn one from each group to get the broadest perspective.
Learning CS history and basic theories might not be directly applicable to your life but the knowledge that comes from it will show up in everything you do and knowing how to leverage it makes a big difference.
The value in learning about compilers, GCs, etc comes from being forced to learn, in depth, languages themselves and concepts behind them. If there was anything you didn't learn in depth in other areas they will probably show up here.
Everything you learn about programming and CS is ultimately dependent on humans and how we interact with computers. Understanding how we think will help you develop long lasting programs that are easier to understand and use.
Depending on your goals it may be useful to learn about the basics of how the hardware works, although that is more related to performance optimization than good design and good code.
Edit:
Learning about so much is a long process and never ends but if you want to write solid code long term, and not just do what's popular at the time, it's the best way to go. Just take it slow and do it at whatever pace works. It works best if you have toy (and not toy) programs to make in each language as you go. After you've started getting a broader perspective, in things like programming languages, it all becomes a lot easier and faster. You'll be able to learn completely new languages in a matter of days and be better at them than people that have been doing it for years, because you can break it down in to its fundamental concepts and all you have to do is learn the language libraries and syntax, which also starts coming quickly after awhile.
So I think your point about being open minded is the strongest. Additionally, you bring up some excellent summaries of what each discipline is about. A lot of the time the failure here is lack of comprehensiveness, or coverage; coverage in space (what's out there) and coverage in time (what's been done historically). Current favorite example is APL: http://isomorphism.es/post/146379365169/unlike-many-language...
But as far as material goes, I suspect pure, bare metaphysics is underrated. Bearing with me, this talk by Hickey quotes a lot of Whitehead: https://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hi...
Whitehead spearheaded Process philosophy: https://en.wikipedia.org/wiki/Process_philosophy
Most metaphysics is crap, but there are a few gems. Whitehead, Russell, Quentin Meillassoux, Wittgenstein, Northrop, Peirce, Kripke. Dealing with fundamental conceptions of space, material, time, cause, and uncertainty can go a long way when it comes to modeling code and understanding when it will correspond to reality. Programming languages give you enough flexibility to reach your ass and fuck yourself over. That needs to be constrained.
tl;dr
We're in agreement, I just like hearing myself talk.
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?
You often hear "The worst code I have ever seen is code I wrote six months ago" While on the one hand that may be true, on the other hand it is trite bullshit. If the depth of your introspection and reflection on past code is "Wow what a pile of shit!". Guess what you are going to be saying about your code in six months?
With a real honest critique you will see flaws and things that you did well. Don't just dwell on what doesn't work. When you see things in your code that work well try to figure out why they work well so that it can guide your deliberate changes to what doesn't work. Also if you don't keep your style constant for a period of time you won't have enough experience with it, nor a large enough body of code to review to have a full view of what works and what doesn't.
Last but most important read the code of others to pick up ideas on how to improve your style of coding. Some of the best bits of my coding style I have picked up from others.
Bracket placement is trivial and can be formatted with a tool in a second or two so play around with that all you like.
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.
For me, pair programming has been the by far best way to improve my coding.
Code that works reliably is (IMHO) better than beautiful code that doesn't work at all, or falls apart under strain.
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.
1.1. Write tests for this problem you solve.
1.2. Do not over-engineer on that problem.
1.3. Optimize later. If the code is clean the performance is usually good enough. If the code is clean you can easily optimize on it.
2. The choice of the language does not matter so much, as long you know the caveats of it. If you use dynamically typed languages you should write more tests covering possible input and output or use asserts to validate it.
3. Know your tools. Try to understand each tool you are using first before using it for your production code.
4. Learn to communicate your thoughts clearly. Honestly it is one of the biggest advantages for any developer. You will write better code as a result.
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.
In general I'd recommend every developer read the book "Refactoring", because the lessons in there are so fundamentally important to development everyone should have them in front of mind as often as possible. Learning about "code smells" and how to transform code from one way of working to another cleanly is hugely important, and that's the "bread and butter" of what good coding is like day to day. Specifically I'd say learn to use extract method and use it incessantly. Don't worry about creating small methods that only get called once (the compiler will optimize for you). Don't worry about creating "too many methods". Don't fret about having "meaty methods" that do "meaningful work" instead of just "trivial stuff", break down your code into components that have semantic meaning, even if they are small components. And if you end up with too many methods it likely means you're mixing up functionality for different things in one spot and need to split it out or you have a more fundamental design problem that needs to be addressed through simplification.
Overall, strive to make code readable and understandable. Learn the magic of well named variables and methods and pretty soon your code will start reading like an open book. Above all else, remember to be kind to your future self and others who might come along your code in the future. It's always tempting to leave a mess because it's "just temporary" or "you'll get back to fix it up later" but try to develop some basic levels of code sanitation so that you're not dumping garbage in the codebase all the time.
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.
Third party libraries, copy & pasted code from Internet, helper functions from a framework, and more often hides critical information the coder needs to be aware. Many inexperienced coders simply put together code and produces working code without good understanding of why/how their code works.
I found that deeper understanding of the code not on the first level but all the underlying libraries and functions, lets me write more concise, bug resistant, and easy to understand/maintainable code.
I think that's actually a very high bar and most code I've seen doesn't "make sense" in that if some non-developing stakeholder looks at it they won't go "oh, ok, I see what this is about" but instead "ok this is a bunch of technobabble that I couldn't possibly understand."
And read or at least skim the "Domain Driven Design" book by Eric Evans. His concept of a "ubiquitous language" is necessary for code that makes sense.
This one is tricky, because (implicitly) it means surrounding yourself with people who are better than you. Which means getting hired on such teams, which is tricky if you can't bring the skills to the table (or mind-hack their interviewing process)... leading to a vicious circle.
But if you can find a way to "quantum-tunnel" through that barrier, and always try to wind up on teams better than those you've worked with up until then -- that's a big step in the right direction, at least.
1. Write code that does something correctly. Let it do that thing for a while, keep an eye on it.
2. Then come back to it with fresh eyes a few weeks/months later and refactor it. Maybe just a critical chunk, maybe top-down.
3. Repeat.
By revisiting your own code and iterating on it, you also update the design patterns you intuitively apply when faced with a problem. And importantly, you upgrade your ability to a better version of yourself.
I've tried giving examples, setting coding standards, etc; but it's really hard to code in someone else's style. We can't all be Jeff Dean or Salvatore Sanfilippo, and their styles/languages/approaches don't suit every intuition. Also, that "great" code is rarely where the project began, so it's like comparing a beginner's first draft with Hemmingway.
Code reviews are difficult for the opposite reason. Even feedback from a much more senior developer usually lacks the contextual knowledge to really know what's going on, so they're just shooting from the hip. Code reviews catch edge cases well precisely because they take the reader out of the "expected" control flow; and they generate lots of "cute trick" advice at a tiny tactical level. They're good correctness & social tools, but not so helpful for self-improvement.
Yup! Also, I would really like the computer to keep an eye on itself by simulating running the program a billion times or something like that.
1.) Uses comments & documentation as a last resort.
2.) Tailors the complexity of their code to the intended audience. There's nothing inherently wrong with 'being cute' or utilising the full feature set of a language if this will make future developers' lives easier. Conversely, if the project is to be maintained by someone that is fresh out of college, KISS is best.
3.) Prefers tried-and-true over bleeding-edge for production-level projects (it's called bleeding edge for a reason, 9/10 times you'll bleed).
4.) Produces code that conforms to what-is rather than what-should-be. Blindly sticking to ideology will perhaps produce more 'beautiful', but not necessarily more maintainable, code.
5.) Is aware of the expected lifetime of the project. No need for defensive programming if this is an MVP that you know will be scrapped in six months time. If this is the backbone of a major banking system then extra checks and being paranoid makes sense.
6.) Follows the conventions of the project and ecosystem. Consistent code is more important than personal style.
7.) Perhaps I'm in the minority here but: is not over-reliant on IDEs. Tools like IntelliSense encourage you to offload part of your mental model to a program; I feel like this inhibits your reasoning abilities.
This includes writing tests for code that doesn't have tests (often requires refactoring or some careful engineering)
And possibly writing tests before you even have the code aka TDD (although I'm not on that bandwagon I have done it at times with great results).
If your code is easy to test it is probably better code than otherwise.
Also depending on the organization a-careful-not-very-productive programmer can be worse than a loose-prolific-code-cowboy but generally that is not the case.
> It is impossible to make your code faster, you can only make it do less. The only way to 'speed-up' a process is to achieve the same result with less code (or really, less cpu cycles).
This is along the lines of less is more, but it is also something that really helped drive home the need to reduce, refactor and refine code instead of just piling more code on top of what you already have.
Less cpu cycles != less lines of code.
For example, bubble sort would be little more than 10 lines where merge sort would be twice as that.
To really understand this, you have to understand how machines work. Merge sort is faster because the processor does less work for the same input compared to the bubble sort. How many lines of code you have is rarely a good mapping of how much work your processor is doing. You have to take into account the complexity (i.e. O(n) or O(N^2)), the framework your are using (just because you have 3 lines of code in a Rails controller, doesn't mean you are executing 3 lines of code, etc.
Another thing that is really helpful is keeping a dev journal: a sort of stream-of-consciousness record of what you're thinking/doing. Include things you thought about trying but decided against, links to blog posts/stack overflow questions, frustrations or pain points with the design, code snippets, just anything really. This can serve as a rough draft for documentation, and forces you to articulate your thought process into words. It takes discipline to keep adding to the journal, and I've fallen out of the habit recently, but it is super helpful.
* Hard to extend/modify
* Confusing to understand
* Full of exceptions and not-quite-repetitions
That last point is what accumulates due to the first two. The best way to learn how not to do the first two is to actually maintain your own code over changes. That teaches you how to make code that you can return to with less confusion and how to code for changes. ( A lot of code patterns are extensible, but grow cumbersome if break a fundamental assumption )
Ultimately, once you learn to avoid universally "bad" practices, you are left with a lot of conditionally good/bad ones. The same practice that makes a solid, extensible structure for one use case is what gives you a monolithic inflexible mess in another EVEN WITH THE SAME CODE QUALITY.
So I'd say focus on that: too often we feel like it if works and is "pretty" it's good, but that's not always the case. Some seeds bear fruit, others eventually give you weeds.
This x1000.
This goes for starting fresh, and coming back to existing code. Too many times I have seen horror that was the result of developers diving right in to coding without spending any time at all having a complete mental model of what they're doing.
1) TDD. Since JS offers so few static guarantees, unit testing your components and utilities as you build is important; arguably more so even than in other languages. Besides test-as-you-go, this includes building things to be testable in the first place. "How would I test this?" should be an up-front question that heavily informs the approach you end up taking. Even if you write no tests, this approach will improve your code.
2) Linting. This goes beyond just enforcing code style preferences. It turns out there are lots of potential bugs you can prevent from a simple static analysis—despite JS's lack of static guarantees—and catching them early will avoid errors and make you more productive.
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.
- If writing in a language with weak ability to do this through its syntax, establish critical runtime invariants. This can also make intentions and system constraints obvious to the reader. I recommend invariant.js for javascript.
- Write your tests so they can all run by themselves, and do not depend on state created by other tests.
- If something is hard to name this usually means the design needs work.
- If you realize something was incorrectly named, take the time to fix it and refactor, rather than imposing the burden of working with an incorrectly named thing on other readers of the code.
Write code so that others can read, understand and maintain.
Don't be a code 'Magician'. Few people other than yourself will appreciate your 'Magic'.
Its ok to be a 'Code Ninja' (whatever that means). In fact I think its demanded that you call yourself that so that you can bypass the recruiting/HR firewalls. Also make sure you note down that you have 10 more years of experience than the age of the technology itself. After all thats what the job posting said is the minimum experience needed.
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.
As for #5, practice makes permanent. Writing lots of poorly thought out code just ingrains bad habits.
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.
Recently, I've been using a tool to show the dependency graph of my program and I've found it to be quite helpful. Perhaps a bigger point is to find and use tools for your environment that really help!
I like others also favor minimizing changing global state, unit testing (increases odds your code's interface is more well designed), code reviews..
Code Golf is about programming as short as possible and it's also a section on StackExchange [1] where you find daily tasks and many people solving them in many languages. You learn so much because...
- You code really every day and you code new stuff
- Motivation is at its max and you want to be faster than the others and have the shortest piece of code; so you still keep coding after your submission to shave every byte away
- And you learn tons of stuff; reason: you spent thinking hours about your solution, how to tweak it etc and suddenly, you see somebody popping in the answers who did it way better than you, so you analyze and study their implementation for hours like you would never read others' source code, adapt their algorithm and learn; especially when it's about array or bit manipulation StackExchange's Code Golf has a vast selection of tasks and some many awesome programmers there; it's just insane, some guys deliver 10 byte long solutions within minutes while I need 2 hours and 150 bytes
- I want to stress–and this might be the most important topic—writing code is one part of coding but there at Code Golf, you learn that the approach, the core algorithm is more important, and can vary so much, so coding is not just writing commands in a text editor, no it's thinking about the right approach and second about writing it down.
- And you see and learn how same problems are solved in different languages
- One thing which you need to get used to are all these golfing languages which are indeed esoteric but every task is solved in all the mainstream languages as well; at the beginning I didn't like all these golfing languages like Jelly, Pyth, MATL but they grew on me and now I understand them better but find them still a bit awkward; so I like more the JS, Java, C, Python solutions; but again you find everything there, lots of solutions in Haskell, Brainf*, PowerShell, TIBasic, x86 assembler, Vimsrcipt, Mathematica and so on
Compared to standard day routine coding where you glue one API to another Code Golf is ultra refreshing and I'd never had so much fun when programming and learned that much at the same time.
I think there are other code golf communities but I think the one on StackExchange is the biggest.
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.
And there is no silver bullet. Advice like "Strong typing" or "functional programming" are not always going to be the best code for the task at hand.
1. "Appear weak when you are strong, and strong when you are weak."
2. “The supreme art of war is to subdue the enemy without fighting.”
3. “If you know the enemy and know yourself, you need not fear the result of a hundred battles.
4. "When cold revenge is served, the dish is always very good"
5. "One man can cut four thousand throats in a single night, if he is continually running."
6. "Only a fool fights in a house that continues to burn."
7. "Pity the warrior that kills all his enemies!"
You have to get your hands dirty trying things.
Always try to keep things simple and short and choose good names. You may have to come back to your code months later, and being able to quickly grok what you wrote pays dividends.
Pick a side project working on something you enjoy. If you work at a corporate job during the day, you may not always have a good opportunity to grow and try new technologies.
> ... In contrast to either of those, everything I write for Servo receives in-depth review and feedback ...
Then you'll start deeply appreciating what good and bad code looks like. You'll see how helpful comments actually are, how layers of indirection help or not, and all sorts of little details that help or hinder your efforts.
You will be amazed on the benefits you get out of these principles. You will be able to describe your entire design in few English words.
Speak to senior developers and get your class design reviewed, a little bit of "why do it this way" questions will help you a understand a lot of things.
tl/dr: Some useful vocab for phrasing questions you can ask yourself about your code, as you write it, in order to improve it.
- static typing programming language is not verbose, if you know how much comments and unnecessary unit tests you have to write.
- focus on conceptual and system level evolve, stop argue and jump between different programming languages for your work just because some of them are cool.
That's my cheap advice. It's terribly annoying but you always come out on the other side having learned something valuable from the perspective you didn't want. Always.
Also, how do you mitigate another leftpad disaster?
It's simple, concrete, and not onerous. The benefits are amazing.
A two-liner is sometimes more efficient and readable than a one-liner.
The comments about writing lots of code are absolutely true, the more you write, the more mistakes you make, the more bugs you have will increase your experience with edge cases and common issues. It's invaluable.
When it comes to actual tips for the code quality itself, a good aim is to make the code readable, as opposed to understandable. Use longer variable names, and avoid single letter variable names. The goal is to make it obvious what your code is doing, you want to limit the comments to sections of the code that NEED them. Far too many developers get the "Comment all the things" dogma, and this just creates a mess. I see this far far too often:
class Person {
// Returns the first name of the person
function FirstName() {...}
}
This is beyond useless. And remember code will begin to smell, but comments will begin to stink, so you want to make them only when clarification or additional information is required. If you find you have to comment all the time, then you need to work on your code composition and naming.For flow, you want to make your code work, then refactor it to clean it up. Refactoring should be something you allow time for on any feature implementation. Never move onto another features saying "I'll clean that up later", you won't, or you will eventually because you've created a brittle mess.
Read other peoples code, and run it through a debugger. While I normally eschew debuggers except in fairly rare occasions, they can be invaluable about learning about patterns that you're not familiar with.
Try ditching the IDE. IDE's make your life easy, and they also often make you ignorant as to how your application actually works. Heavy handed namespacing languages like C# and Java are a little trickier to work with this way, but you will learn so much more about your application than you will being spoon fed everything. The reason this is important is something will eventually break, and it's far better to understand those underpinnings when building the app than when something breaks when you're staring down a deadline.
Read books about patterns, languages, etc..., and re-read them after you have been using the techniques for a while. Become familiar with the GoF design patterns, so you understand them, and understand when they're useful. Do not use them as a gospel. Pigeonholing in patterns where they don't belong will create worse code than if you hack together your own more appropriate method. But definitely use them when they fit.
Create an open source side project, and put it up on github. Good code involves interacting with other developers, and even if only 10 people are using your repo, you will learn all about pull-requests and merging, etc...
Teach what you know. You will be amazed at how much better you will understand a lot of concepts once you have to explain how they work. Even if it's just to a rubber duck.
Speaking of rubber ducks, when you get stuck on a problem, or you have a bug that seems illogical and you don't have someone handy to ask for help, explain the issue to a rubber duck. On our team, we'll often call another developer over, and while we explain the problem to them, before we're done explaining, we've already figured out what's wrong. Language uses a different area of the brain, and I suspect that by talking things out, your brain will approach the problem in a way different from the state we use for coding. (this is pure blatant speculation on my part).
Unit test, unit test, unit test. However, ignore coverage metrics, that's a huge waste of time. When you're starting out, write all the tests, but be sure to observe all the instances where the unit test actually identified an issue in the application. You'll always want to unit test your business logic, but avoid testing everything and never test for things that are impossible to occur. Also never test anything but your own code, I've seen fat too many instances of unit tests that simply add a suite of tests to jQuery or some other library. But do assure that you are able to handle bad input, regardless of here it comes from.
Don't be clever. Sure, you can make an easily readable 7 lines of code into a clever one liner that only you understand because you're oh so smart. But don't do it. When you're writing code, you're doing it at the height of your understanding of the system at the time. So even if you're the only eyes that see it, as you move on to other projects, you will no longer be as 'smart' in that codebase. This is especially true when you switch between languages frequently. Beyond that, you probably won't be the only person looking at your code, so write the code (as much as possible) to allow a non-developer to be able to get a sense of what's going on.
Avoid magic strings. What this means is that a string generally shouldn't be used to determine the execution path of code. A simple spelling mistake can be very difficult to detect, and there aren't any tools that can assist. Enums, constants, or named integers are far safer.
Take breaks, get exercise, and don't try to code for every waking hour. If you're in flow, and making real progress this is fine, but if you're just working the hours and aren't making a lot of headway, a good nights sleep will go a lot further than another 4 hours of a fatigued brain. Seriously, sleep at least 8 hours, your ability to learn and remember is entirely based on your REM cycles, when the brain essentially writes from it's cache to permanent storage and cleans up the toxins from the day. Without sufficient sleep, you will not remember what you did, and you will not learn from the mistakes you made and new understandings you would have otherwise acquired.
Never copy and paste from SO or other snippet that you find to solve your problem. Read the example and understand what is going on in the snippet. Then hide the browser and type it in as you understand it to work. I came up with a rule for my first team 15 years ago, and that was to never insert code into a project that you don't understand. If you don't understand it when you add it, you won't understand it any better when that code breaks.
First, slow down. Slow, slow, slow, slow down. Take the time to test and to write tests. Take the time to think about your code. Do it as right as you can the first time. Don't let anyone tell you you don't have time to do it -- you always have time to do it.
Your boss might say "deadline is 5pm", but push back if you can't do it right. It's always, always worth it to slow down. Build testing into your timeline estimates. (My rule of thumb: double the estimate. 1x for writing code, 1x for testing).
Second, don't overwork yourself for no reason. I used to be the support engineer for a startup. Half of my job entailed fixing all the bug reports that came in each day. I would reproduce the bug, add a test, and push a fix as quickly as possible. I used to come in at 9 and stay until 7 or 8pm. One day I cleared out the queue. No more bugs in the list. The next day I came in and had 10 more items. Realization #1 happened there.
I sent an email to a colleague at one point, and he didn't respond until 11am the next day, but I barely noticed. And realization #2 happend then.
The realizations were simple: 1) there will always be more work tomorrow, and 2) nobody realistically expects a response within minutes. In fact, if you send an email after 4pm, you probably don't expect a response until the following morning.
Both of those realizations caused an attitude shift: I leave work at 5, 5:30 every day. I spend more time at home with my fiancee and friends. When I come in, if I have a few more emails than I did the day before, that's fine. They're not expecting instantaneous responses -- and if they are, that's their problem, not mine.
Each support ticket got resolved in 24 hours or less -- far exceeding our customers' expectations -- but I went from working 60 hour weeks to 40 hour weeks. Much happier, much more productive, much less stressed out, and our customers will still exceedingly happy.
A friend of mine works 60+ hour weeks and checks his email constantly, even when he's not at the office. I told him to just wait until morning and email them then. He says, "If I do that, I'll never get anything done, because I'll have 100 emails from my boss in the morning." Well, that's not his problem -- that's his boss's problem. He should go back to his boss and say "If you want me to get X, Y, and Z done, then you need to either email me much less frequently, or get more headcount on the project."
Don't overwork yourself. Don't work more than 40 hours a week without extra compensation. Don't stress out about work -- I guarantee they're not stressing out about you.
Finally, eliminate the phrase "it works for me" from your vocabulary. When someone sends you a bug report, whether it's a customer, a manager, or a fellow developer, your first action should be to confirm that it is, indeed, broken. They would not send you a bug report if there wasn't something wrong. It might be your code, or it might be them, but you owe it to yourself to make sure you have bug-free code, and you owe it to that person to give them the benefit of the doubt.
And one thing you do NOT want to do is go back to someone and tell them THEY were wrong, and then have it turn out that it actually was a bug.
Do your due diligence and rule out a bug. If you can't recreate it, go back and say "Hey, I'm having trouble recreating this. Can you give me more information?" Only when you have ruled out a bug by reproducing the behavior, do you go back to your customer and explain that they made a mistake or something.
Those are the three biggest mistakes I see with new developers (and even people that aren't developers). Slow down, don't work too hard, and start off with the assumption that the customer is right.
My two cents.