The laws of the computer professionnal

For those who neglect the laws of computing, the retribution is deadly: they will go home late and depressed while their system is not working !

Living

Sometimes, you're not in the mood

Do something else, you'll get back to it tomorrow. You may think that you have to do it now, but the point is if you're not in good spirit, you will fail, leaving your code in a state worst that it was at the beginning of the day.

Take care of your relatives

You will need their strength and attention at some point, so having something to do is no excuse for negligence.

When you're on your computer, you often need not to be disturbed, something that may be difficult to explain. Be patient, explain. Explain again if you have to. Give you a deadline: if you're not done at 8pm, you stop and you'll get back to it tomorrow. That way, you can tell your wife (or your husband !) when it's your turn to take care of the children.

Be in good health

Take some sleep, do some sport. You'll code faster and better, and last longer.

And by the way, if you're very good, you can write about 100 lines a day. Typing them takes half an hour max, so this is not really a question of time spent in front of the keyboard, this is a question of time spent thinking. And you can think while jogging, swimming, biking or whatever.

Writing code

Inside the code

Aphorisms

  • Build for evolution
  • Be consistent (in naming, parameter orders, where prerequisites are checked, etc...)
  • If you're confused by your own code, think about how you named things.
  • Ship early, ship often (Linus Torvalds ?)
  • Refactor early, refactor often (Me)

Naming

Give meaningful names

Obvious. Which code is understandable:

for i in seq:
    i.uc()

or

for pupil in pupilList:
    pupil.uppercasifyName()

Avoid technical names

In short, names should describe function, not structure, what is the object intent, and not how it is build (even if, as coders, we always need to think both in terms of how are objects built, and what they do).

Not only should you use meaningful names, they should be taken from the problem space, not the implementation space (or put another way, they must refer to the higher semantics of the code, not the implementation details).

Here's an example: you have a dictionary (a string -> data mapping, sometimes called hash, associative structure, etc...), that maps names of students to their classes. You can iterate over the dictionary getting (key, value) pairs. So you may be tempted to write something like (example in python notation):

for key, value in dictionary.items():
    doSomething(key, value)

But here, key value are somewhat meaningful names, but they refer to the implementation, not the actual object. If the code fragment is inside a function, at some point you won't remember what keys and values are. A better choice of names is:

for studentName, studentClass in dictionary.items():
    doSomething(studentName, studentClass)


Minimize the number of names

A classical mistake is to give a different name to a temporary variable, for example:

for accessor in iterator:
    obj = accessor['stuff'].things.bar['foo']
    obj.doThing(obj[3])

Clearly, you're working on "foo", so why introduce a new name ? Instead, write:

for accessor in iterator:
    foo = accessor['stuff'].things.bar['foo']
    foo.doThing(foo[3])

Rules for id

Ids are a way to address a particular object inside a collection, for example a primary key into a database.

Rule #1: use an abstract value, in short a number that doesn't mean anything, as opposed to a field of the records of the collection. The reason is you never know how things will evolve in the future. An obvious example is you have a collection of people, using the name as the key will break as soon as you have two people with the same name. So you may try to be smart, and use for example the social security number. After all, it is supposed to be unique. The point is, one day you'll have to enter into your collections a record with someone you don't have the full number, and it will collide with another similar record. Or you'll have to enter robots along the humans into your collection, and they don't have a social security number. So go for a number, it doesn't depend on any constraint in your problem space.

Rule #2: don't assume the numbers are consecutive. If you do, you'll be tempted to write something like

for(int i = 0; i <= collection.length(); i++) process(collection[i])

But, at some point you'll want to remove the element at position 23 out of 37 in your collection, and this will break your above code.

So, although you are using consecutive numbers as your ids, do not assume they always be. To iterate other your collection, use

 for(iterator i(collection); i; i++) process(i.getCurrent())

You get the id(ea).

Avoid technical objects as much as possible

They often reveal a lack of abstraction. Si also the next law.

Limit the number of objects and types

Could you understand a novel with 100 important characters and 50 places ? Probably not, so why are you trying to write code with 50 types and hundred of different names ?

Write clear code first, optimize second

You may have to change and refactor your code, but this is not a problem, since you wrote unit test, right ?

Dealing with errors at run-time

Always check the return of your functions, or use a language with exceptions

You can never assume that "nothing could go wrong". May be this is the case when you write the function, but one day it will change, and you won't be able to go through every call to check.

Re-raise an exceptions you caught, not generic ones

(when you re-raise an exception). Otherwise chances are you won't be able to make the difference between the different problems while debugging. Or worse, in production.

Refactoring, making code evolve

Refactor

Refactor early, refactor often.

Write for refactoring: it means defensive programming, and automated testing.

Put as little new code as possible into the path of existing code

You have for example:

for customer in customerList:
    sendBill(customer)

Now, there is a new requirement, if this is the discount day, you want to add a discount to every customer, depending on its buying history. There are at least two ways to do it (assuming customerWithDiscount is only called at one place in your code, and that it returns a new list of customers, enriched with the discount):

1)

for customer in customerWithDiscount(thisIsDiscountDay, customerList):
    sendBill(customer)

(with customerWithDiscount testing for thisIsDiscountDay)

or:

2)

if thisIsDiscountDay:
    customerList = customerWithDiscount(customerList)
 for customer in customerList:
    sendBill(customer)

Which one is better ?

I believe this is 2). The reason it that, if you have a bug in your (newly written) customerWithDiscount routine, in case 1, the bug will fire even in the case your routine is useless, that is thisIsDiscountDay is false, thus potentially breaking existing code. In case 2, the only thing you have to do not to break your existing code is to be sure of the one single test line, something much less difficult from a cognitive point of view, since you don't have to hold both old and new code in your mind.

If customerWithDiscount is called from several points, however you should test inside the routine rather that before calling it, to avoid duplicating the test through your code, and this should take precedence.

Outside the code

Use a version control system

Obvious.

Commit early, commit often.

If the modification you did has a strong semantic, do commit now. Here are example of modifications that have no semantic:

"Added line n += 1"

Yes, I can read your code, this doesn't help. You could say "BUGFIX: line counter wasn't incremented"

"1) data are now read from the config file 2) Added the ability to use ssh for communicating with the server"

You should have commited each modification separately.

Reread your code before committing

Before committing, perform an XXX diff, where XXX is your version control system. The minutes spent rereading your code aren't lost.

Write unit tests

So

  1. You have a clear understanding of what your code does
  2. You can refactor.

System administration

Put a local DNS resolver on every machine

Never put something into production on Friday

...or you may very well have to neglect your family on Saturday, while pissing off your customers and colleagues.

If a machine seems to do nothing and has no load, it's waiting for the network

In that case, tshark and netstat are your friends.

Comments

<comments/>