What most young programmers need to learn
joostdevblog.blogspot.com
joostdevblog.blogspot.com
To my experience this is a common symptom of not using a version control system. You change a line of code, but you want to be able to undo if it doesn't work, possiblay half an hour later when you changed other places of your code (so your editor's undo is of no use).
I also see this from people who are using VCS, but that's mostly because they are overcautious and/or have not really gotten "warm" with the VCS.
A final, very small percentage are familar with VCS and confident with their code, but leave that commented stuff in there accidentally. The usual cause it that they have not (yet) acquired the habit of reviewing the diff before committing.
Plus I like to add some perf test that shows the complicated version is faster than the simple one that I can rerun after major upgrades to the lower level system e.g. when upgrading from java 1.5 to 1.6 I could remove some complicated code because the simple one was then as fast due to an improved JIT.
Don't aim for 100% test coverage aim at using unit tests to develop faster and more efficiently and the test coverage stats will come automatically.
I personally need to improve my TDD, but I am quite happy with my test driven bug fixing approach. When working on a bug I first turn it into a JUnit or selenium test, and then fix the bug and make the test pass.
Having random scribbles, alt solutions and other "shitty" comments while working on a problem is definitely not a sin if you clean them up before pushing them forward.
An actual comment is of course helpful, but when people check in code that has been commented out it usually has no explanation at all. Thats the confusion part.
I'm talking stuff like this...(and I believe so was the author)...
//int foo = 10;
int foo = 20;
if (bar)
//if (foo > 0)
{
foo++;
}
//else
//{
//foo--;
bar = false;
//}
What if there's some functionality in a method you're not sure you want to take out or change or not, but you've also added to the rest of the file, so you commit it with a small code segment commented out and a short explanation? That's not hurting anything and it'd be a PITA and overkill to put it in a different branch or make a separate commit only including it or something.
I don't get this; stashing changes, branching, even just copying and pasting something from history in git, are all very fast for me (using git in emacs with egg). Granted, I can comment-dwim with M-;, and I've conditioned myself against "just" commenting out code, but still.
Yeah, that could probably describe me. Reverting a particular piece of code back from VCS seems like more work (the kind of "I need to think about it" work), which is enough for me to leave bits of code commented when working on some particular task. I never leave them there for long - usually only until I can get the new piece of code working and tested. The commented code serves as a reference/fallback.
I'd rather be intimate with git.
I think that's partially because many developers will "learn" a VCS when working on a school project or hobby project by themselves. For a personal project, often all you really need is dead-simple versioning, and even then you may complete an entire project with just a linear series of commits on a single branch. It's easy to avoid really learning a VCS in that environment.
Code in comments may also occur as a symptom of poor VCS habits, but that's not always the case.
1: TWGIF - That's what git is for
That's okay though. I visually review the diff before I commit (almost) every time.
This was something that didn't dawn on me until reading Matz's philosophy of "programmer happiness" when designing Ruby. Code seems sterile, something that you just put into place in a logical manner until it works...in small class projects, you don't realize the mental toll it can be to read through a messy code base...But being unhappy about a monolithic, massive codeebase is easy...the problem is tha when working on small projects, you don't notice the mental tax of unclear code that can drain your happiness and productivity.
It took a long time of professional coding for me to realize how much thoughtful design of code could make me a happier coder, just like the adept use of language contributes to happier communication in all other areas of life
I'm happy you are not afraid to call yourself "junior". Humility goes a long way to becoming a great programmer.
Asking permission is obeisance but it is also communication. Whoever you asked may have knowledge of dependencies upstream or downstream. By asking, you gave that person the chance to add a little context to the change you propose to make and, quite possibly, prevent the team from working overnight to correct an issue you created. Even with 25 years in the field, I still ask other engineers about changes I want to make. Quite often we talk through any ripple effects before I set hands to keyboard.
Be especially careful in modifying interfaces, library calls, etc. Basically, any module that other systems depend on should be modified with a lot of caution and testing. Your caution should grow at least linearly with the dependency graph.
Over time you will develop a sense of which changes you can make without asking, which changes you should talk with others about, and which ones to stay clear of.
Your primary responsibility is creating good software. Your second responsibility is staying employed. Don't fret over every oddity you see in a code base. Refactoring is good for all the reasons people listed in the comments. Figure out which ones have the highest value and talk with the other developers about them. ("Live to fight another day")
If I am working on a task and code got a bit too messy for my liking, I'd simply refactor, it's simply part of the task. Working != complete. If something goes wrong, it will be easy to revert given it is committed in a sane way (i.e. not loads of unrelated work under one commit).
It's a bit different if you want to do major refactoring (taking multiple days). But with small ones, just do as you go ;)
1. Making the code cleaner is reducing technical debt. This will help in the long run, and I just sort of do it as I go. It's like tidying the house - it is more efficient to do little things regularly than to have a giant mess to deal with later. The little things make it easier to do something later, because you don't find yourself yak shaving as often to get to the main task.
2. In my personal and observed experience - doing the fixes at first seams like heavy task. "it works, this is just grunt work", but as you get practice at it, you'll find that it helps find bugs, it becomes a habit, and you just do it as you go.
3. It helps me understand the code better. Even if I wrote it. Just because I got it to pass tests doesn't mean I know why at first - but cleaning up the code makes me realize edge cases and what's happening.
One thing that helps is to keep in mind the axiom "all code sucks. Some code is useful" (to paraphrase a famous saying). I know when I code stuff, I tend to do a throwaway implementation or two first, just to wrap my head around the problem, then keep one. After a few months, I'll revisit and understand even better what to do, and rework it again. Then maybe it's decent.
I'm sure there are people out there who can do it great the first try, but they aren't that common. The secret is to not wrap your ego/identity as a programmer/understanding of accomplishment up in the code you've written, but in your ability to solve or lessen the problems that come up.
One big improvement I've made in my code over the years is that I take the time to improve my code. Continuously improving code quality does lead to high code quality, and high code quality reduces maintenance hours (both amount needed and time spent).
If you're writing the code which will live at least a month — definitely. But if you're writing a quick hack of a project or test that you're know won't be around next week, it's often a real waste of time and effort.
And I'm not writing about that theoretically now: for me personally, it's a real problem. Whenever I sit to write a simple dirty hackey thing, I always find myself a few hours later googling for the best way to implement unit tests for this particular case or something like that. Which is sometimes educational, but is very distracting.
Writing quick hack code is a good thing if you are in an organization that is disciplined enough to throw it away after you've learned from it.
This snippet should only live in one place:
if (something) {
for (blah blah) {
something_else();
}
do_something();
}
on the other hand, this code can be copy+pasted as much as you like: do_something();
do_another_thing();
another_call();
some dogmatic programmers will want to place those last three lines of code into a separate function and instead copy+paste that. But I've found that doing that sometimes makes the code more harder to read.if you copy:
do_something();
do_another_thing();
another_call();
a few times in your codebase, but decide that you want to alter that a bit: do_something();
perform_new_feature();
do_another_thing();
another_call();
you can hurt yourself by forgetting that that alteration needed to be done to each instancejust easy to maintain if you do this:
function dodoan() {
do_something();
do_another_thing();
another_call();
}
then copy paste dodoan() as many times as you wantthough i do agree all rules have their exceptions and should be handled in such a way that allows for future alterations stead some obsessive mindless adherence
def a_and_b_and_c() {...}
def a_and_b_and_c_error_checked_internally() {...}
def a_and_new_and_b_and_c {...}
def a_and_new_and_b_and_c_error_checked_internally() {...}
def a2_and_b_and_c
And so on. Basically you are just putting a memorization task on calling the names rather than using the underlying bits well. Learning the balances around this is one of those "craft" bits of programming.Your parent's comment addresses by far the best argument for the principle, which is that any time something is likely to change in a snippet, copy-pasting should be undertaken rarely and thoughtfully. With the recognition that nearly any given snippet is likely to change in some way, we can conclude that nearly any copy-paste is "bad".
Your comment is then a great argument against the thoughtless application of the principle, recognizing that yes, granted, nearly any snippet will change, but the required changes may well not be uniform.
Personally, I think it still makes sense to pull out shared behavior for the period of time in which it is shared. During that period of time, all you have is a guess that the code might eventually diverge. If there comes a point in time when you do want the code to diverge, it is straightforward to copy-paste it back out, if branching or creating a similar method with the differences is a worse option. On the flip side, if you haven't pulled out the shared behavior, and you discover that you want to change it everywhere in the same way, it is far less straightforward to go find all those places, or even be aware that you need to do so.
Another point is naming by purpose rather than behavior. To continue your example, it makes sense to ask why one method checks internally and the other externally? What different purposes do the different checking styles have? If there are good answers to questions like that, the methods can be named better, and it becomes less about memorizing name than understanding when to use which.
Of course none of this is at all black and white, and I think you're spot on that this is one of those craft bits, and among the most important!
I think it comes down to this - when I first learned to code, it was hard enough to keep track of a couple different functions. As experience came, what I could track and reason about and keep in my mental model grew, so now that I've got some experience I can see more of how the whole system will grow and interact. I'm not really smarter per se, but rather I just have more practice as putting it all together.
I do think we (programmers? people?) have a tendency to avoid inspecting the experience we already have. We thought hard about some pattern a few times and came up with some instincts based on that experience, which is great, but we should make sure to double-check those instincts from time to time.
Analyzing this further would require having a more fleshed out example.
For example, how would a developer know to break [insert functionality] into a separate method? It is not always obvious to junior developers to break a method into smaller chunks, especially when they understand every line of code written. They usually recognize the problem once it is pointed out, but it doesn't often register beforehand.
As a junior, you're probably spending most of your time (and a lot of it at that) just trying to make the dang code work. Making sure it's written in a way that other people can maintain it etc is secondary, because who cares if you can maintain it if it never even worked in the first place!
When working with a new junior, It's important to expose them to existing (good!) as well as bad code bases. At the same time, let them write new code on things that aren't super business critical. You have to accept that it's going to take them some time to learn, and you should just work with them until they don't have to think about breathing any more.
But, from years of managing summer interns, the biggest surprise to new programmers is the amount of non-code stuff that needs to be done alongside actual coding. Doc, comments, peer reviews (and the associated rework). The sooner they learn this is part of the job, the sooner they'll fit into the team's workflow and contribute at a high level.
You can't expect to teach them and see results immediately. But teaching them (and supervising / reviewing code) would certainly help to make the learning period shorter.
And I'll admit that self-discipline was sometimes an issue. When you take responsibility for a complex project when young and inexperienced, you feel a lot of pressure to perform and get it working so you can tackle the next task. Maybe us noobs aren't used to this sort of pressure?
I had the chance to do a code review with a few of the top engineers in the company, and it was tremendously helpful. We focused on a small part of the code that was responsible for a performance bottleneck, and I didn't realize how sloppy some of my code was until we walked through the function. There were really silly redundant things that I thought I would never have written in my right mind. I was trying to optimize a function where some of the simplest code was redundant and actually contributing a bit to the performance problem. They were nice about it and said it was not unusual and that code reviews are great for spotting such things.
(edited)
I made similar observations and came up with a list of "coding commandments": https://larsxschneider.github.io/2013/08/25/ten-commandments...
I think I started reading after @edw519 said something like he reads his code everyday before going to sleep. While during writing my focus is on making the thing work when I am reading I am more critical of my code. Not as good as having a review but still helpful.
Also I think reading code of other programmers is a good exercise. Reading different styles of books is already recommended to writers and I think that programmers can extract same benefit by reading code of different programmers. If nothing else at least it will develop your debugging abilities.
You will dissuade them from ever taking up this profession with that attitude.
I was attracted to this because of the opportunity to create things, not because other people wrote stuff and now I have to read it.
1. Lack of knowledge of the business domain leading to an inability to understand the high-level, conceptual view of a system
2. Poorly named variables
3. Poor code organization
4. Loose cohesion in objects and functions which in part flows from bad naming
IME, these issues can be fixed on an accelerated timetable when experienced developers help mentor younger ones.
To use a metaphor: It doesn't matter how delicious of an apple you have (the data), what truck you use to transport it (the back-end code), or how nice of a store display you put up (the front-end code), if you don't store them properly along the way (the schema).
As still the best "small" language to teach fundamental principles (everything is a first-class value, symbols are references to values - naming, procedure composition and nesting as the basic building block, ADTs, immutability of the data, evaluation strategies - eager and lazy, and what is meant by "mostly functional language", etc.) and shapes of data structures (list, three, table).
It will pay back with any "stack" or a "framework".
Haskell.
To learn that static typing done right (type inference) is a very clever feature, but it catches only simple errors (it cannot catch flawed logic or wrong abstractions), so it is not a silver bullet, and, perhaps, to realize why "extremes", like "pure-functionality" or "lazy language" are rather unnecessary complications than big gains. And that monads are mere accidental, awkward ADT to ensure an order of evaluation in a "lazy language", where it is undefined by definition.
After that one would find everything in industry is rather easy and boring and develop a healthy aversion to Java and other "packers" stuff.
Monads are great in that disparate things like lists, sets, IO, control flow, state, optional values, and even functions are all instances of a single ADT which is useful enough that one can write reasonable code abstracted over it, and Haskell provides the mechanisms to do so.
Don't you think that at least in a strict language, this would be rather over-abstraction or abstracting for the sake of abstraction?
Monad make sense only within a language with Normal (instead of Applicative) order of evaluation, to ensure that one computation (or action) "finishes" (being reduced to a value) before another (>>= and >>). return is for the type-checker.
All I'm saying is that monads are a useful abstraction regardless of whether or not they are used to encapsulate effects. I use Traversables, which have a bind operation, all the time in Scala, and it is an effectful language.
To the beginner I'd say: study Scheme and Haskell. Understand them. Once you do, look at other languages. Everything should be familiar. Now you have to decide if you want to spend your professional life always learning different ways to do the same few things there are to do that you learned from Scheme. YMMV but yes it gets boring and yes it will burn you out.
Maybe we should all play the normal game instead of turning on god-mode Scheme?
I'm not going to lie: I don't want to be bored or burnt out.
It's as if you found a way to make cash appear from nowhere. It would be hard to find a reason to work for money.
Same deal with Scheme. It lets you do anything, easily, but it makes it hard to find a reason to want to do anything.
To be fair I wouldn't blame it on Scheme, more on my own brain, but still, beginners should look at Scheme right away so they can get to the "I can do anything, now what do I want to do, if anything, with this tool" part faster.
"A little discipline now will save a lot of discipline later."
This goes for anyone in any walk of life and in any profession.
One tip I'd offer is: You are not inventing something by naming it, you are describing how it's used (based on its behavior).
If it's hard to name, one possibility is that it is not designed properly or that it's doing too much.
"In this case however it all still makes sense to be in one class, but the class simply grows too big." - would be nice to see some examples, I bet anything can be split in a nice way.
- The inability of younger pears to ask for help or otherwise design reviews before they write the code.
- Super hero programming which most of the time boils down to the above and various forms of pseudo-optimisation that are difficult to read. E.g. messing with inheritance and directions: UpwardEngine --- inherits ---> DownwardEngine, instead of using a BaseEngine, because it's saves one class definition (and improve dispatch performance...)
Another advice I would give to young developpers, is that getting as much as possible bits written helps getting better. The thing is that reading code to refactor and debug given the chance to do it right is more difficult and more rewarding in terms of skills.
I'll add to the list the most extreme form of 'not written here syndrome': 'I didn't write it syndrome'. The programmer sees themselves on missing out on the fun of solving a task by using a library (or reading the relevant framework docs). When experience teaches you that the real misery comes down the line when you need to support your hand-rolled physics engine lacking in tests...
Maybe I'm just not a very good programmer...
Whenever I open a file, I try and take a quick glance to see if there's anything that could use refactoring before working on the actual issue (if it's either a particularly large file or one that hasn't been touched in years I'll check Sonar[1]).
http://www.amazon.com/The-Clean-Coder-Professional-Programme...
I'm working my way through it now (1+ year of professional experience) and it is a magnificent way to improve the quality of your code. I read it off and on, my goal is only 40 pages a week so that I'll make sure to find the time to do it (I'm doing a masters program and enjoy living in NYC too so setting huge goals doesn't work well for me).
Every time I crack it open, I find myself inspired to write better, clearer, and more concise code. Sometimes you just need a nudge to get back into doing things you already know you should be doing.
Finally, constantly learning, I think, is the best way to become a proficient, and then skillful professional software engineer. Many programmers become proficient and then level off. And that's good enough. But if you truly wanted to become one of the top 5% in your field you need to do something called deliberate practice. Reading 'Talent is Overrated' [1] really exposed me to the theory of constantly challenging yourself in order to grow. I really recommend it, I find myself trying to apply the theories to all areas of my life.
[0] - http://www.amazon.com/Code-Complete-Practical-Handbook-Const...
[1] - http://www.amazon.com/Talent-Overrated-Separates-World-Class...
http://www.amazon.com/gp/aw/d/B007NZU848?ie=UTF8&redirectFro...
While the code worked reasonably well, there was no indentation, spacing, comments and variable names longer than one letter (perhaps because my first language was GW-BASIC).
Anoter trick was always to manualy unwind any loop counters if you had tp break out of a loop as the GWBASICS had a memory leak.
Another point to make: while every language has it's own idiom and style, there are also lots of "clean code" lessons that apply across languages, so guides about, e.g. python, translate somewhat into objective c.
I've found that reading books—such as the Pragmatic Programmer series, Effective Java, and others—helps me to understand the rationale behind what otherwise seem like strange (sometimes even boneheaded) design decisions. Even oft-derided patterns (such as the "FooBuilderFactoryBuilder" so often seen in the Java world) make sense when understood in the context of the problems they're trying to address.
If I had only read Dickens, I might think writing a novel entailed finding only strange characters, pointing out social flaws. Keeping a thematic style that was about confusing light and dark with the normal associations. And so on.
If I had only read Twilight, I wouldn't care much about dialog or character development. I wouldn't understand that there are things you can do thematically without exposition.
If however read Dickens, and Hemingway, and Tolkein and Dan Brown, and Stephanie Meyer and ... I would have a different understanding of what could go into a novel. I would be able to see where story arc intersects with bigger themes and character development. I would see different ways of structuring sentences, paragraphs, chapters, and even whole books.
None of this reading of course will make me a great author, writing does that, but a wide exposure will certainly help me understand where my writing is working vs where it isn't, it will help me understand how to structure things, help me shape my own work.
I consider the same to be true of code. The folks that only read WordPress code have a very limited understanding of possibilities. The folks who have read that, and rails and django, and jekyll and flask and ... will see a wide range of styles, ideas about structure, and so on.
An aside: Wordpress has some pretty ugly parts, but there are some ideas in there about structure that I have always liked. Particularly considering it was designed and written during the "explore and figure out what works" phase of web apps, when the industry didn't really have a "best practices for the web" that included lots of experience with what does and doesn't work.