RE:

This is probably not really for first-few-months beginners...

  • I did not yet realize how different 1-month personal projects are from large-scale projects made by big teams. Suddenly you don't know all of the code anymore, you have to make assumptions about what other code does, have to follow conventions, the percentage time spend finding bugs goes way up and all timescales in general increase by an order of magnitude.
  • I thought type systems are useless, or are only useful for performance. In reality, they help rule out a lot of bugs at compile time, which is absolutely where you want your bugs ruled out, and they are documentation that doesn't get outdated.
  • I didn't try to fail as fast as possible. It would seem kind of convenient if the program kept running if I'd forget to pass an argument, or would get null for a a non-existent hashmap key, or when dividing by zero. At least the program was still running, right? Well, it shouldn't be. A clear error is much better than a wrong/undefined result in most cases. If you're unsure about something, make it fail loudly until you are sure. In development, you'll want to move all your errors in this direction: continue with wrong result -> fail with stacktrace -> unit test failure -> compile time error.
  • I almost always underestimated the time needed for anything (I kind of still do). I don't know if that's specific for programming. But it takes a lot of experience to be able to make any kind of reliable time estimate.

One day, you will wish your problem were as immediately apparent and easy to find as a missing semicolon...

As an example of the third point:

  • "Continue with wrong result": e.g. Javascript uses toString as its hash function for maps, and does not do equality checking, and returns undefined for missing values, so if you use the wrong key by accident, you'll later find out the result is undefined if you're lucky. If you're not lucky, you get the wrong object back and pay 10.000€ to the wrong person. You could move this to "fail with stacktrace" by using something like Python (of course, in the browser, you probably can't...).
  • "Fail with stacktrace" might happen in many cases where you're solving a bug and don't want it to come back. Lets say your method constructs an SQL query, but when using it with some combination of patameters, you find that you forgot a space and your database rejects it. You then write a unit test for various parameter combinations, which causes this and any future SQL syntax errors: "unit test failure" reached. (Which should in turn prevent you from merging the feature into the main code).
  • "Unit test failure" could be the above case, which might become a compile-time error by using a type-safe query builder. Or you might be getting a null-pointer, so you switch to a non-nullable type (in e.g. Kotlin, Swift, variables can be nullable or non-nullable), or you use Java's crappy approximation Optional (or primitives).
  • As an example of going straight from "continue with wrong result" to "compile time error", and to illustrate my second point, lets say you might make the mistake of comparing Person.id with Customer.id, which does not make sense because they're different tables. Well, if they're not compatible, they're not really the same type of thing, are they? But chances are that the are both integers in the code. So instead you convert them to Id<Person> and Id<Customer> respectively. This won't be possible in a dynamic language, and it will only help a little in e.g. Java that implements .equals(Object) by default. But in e.g. Rust, you just don't make Id<Person> and Id<Customer> comparable, and it will simply fail to compile if you ever make the mistake. More info.

I know this is not really beginner level, but it took me long to realize, so I want to save you a year or two!

In reality, they help rule out a lot of bugs at compile time

Hence the joke about shooting yourself in the foot in programming languages. Pascal won't let you shoot yourself in the foot. Any decent compiler shouldn't be letting bugs compile and run in the first place -- and really interpreted languages that have a pre-parsing stage should be implemented with the same mentality.

... which is why it's so annoying that so many languages do not take this approach and just blindly plod on as if nothing is wrong past stuff that flat out shouldn't even compile.

But then I spent a decade working in ADA. You want a language that doesn't let you compile mistakes?

I almost always underestimated the time needed for anything (I kind of still do). I don't know if that's specific for programming. But it takes a lot of experience to be able to make any kind of reliable time estimate.

"Ach laddy, ya didnae tell 'im how long it would REALLY take, did ya?!?"

Call me Scotty

One of the earliest lessons I learned in programming was to "Mr. Scott" all my time estimates. You figure out how long it should REALLY take to the best of your ability.... then you double it when talking to the client.

A good Engineer is always a wee bit conservative, at least on paper.

It is always better to deliver ahead of schedule than to be late. It is very easy to give a time estimate for if everything goes right -- rarely does everything go right even from the very start, so you HAVE to give yourself that wiggle room... doubling the estimate is typically overkill, but it's a responsible approach to the problem.

... one that is a constant battle what with the "we want everything yesterday" mentality of clients with utterly unrealistic expectations.

Just don't let such doubling make you lazy because "we've got the time" -- what you tell the boss/client/what-have-you and the schedule you plan for yourself should be two separate things!

Reply to this…

(6 answers) Take me to the question