melp.nl

< Return to main page

My top 5 dos and don'ts of self-explanatory programming

I have gathered some of the less obvious do's and don'ts in how to make your code more explanatory to your coworkers. I left out the more obvious like writing doc-comments, using coding standards, etc. Those go without saying. This might be considered "drm's php tiplist revisited" ;)

#5 Use one exit point in a function. Except for exceptions.

This eases debugging, and makes your code more readable. Since the flow of the function will always end in one point (and not break flow somewhere in the middle) it is easier to understand what it does. Use a standardized value name to store the return value for the function, like 'ret', which you will not use in any other circumstance. Always initialize this value at the top of the function with a default value, even if the default value will never be used, so it is visible that your function will be busy determining what that value should be.

#!php
function doSomething($param1, $param2) {
    $ret = null;
    if($param1 == doSomethingElse($param2)) {
        $ret = $param1;
    } else {
        $ret = $param2;
    }
    return $ret;
}

If you have a function or method that might enter an unknown state, or unexpected state throw exceptions, that's what they're for. Check your input and be aware that even if your function is called only internally, you never know what people will get to work with the code and you therefore never know what they might think is appropriate to feed it.

Don't:

#!php
function checkStuff($foo) {
    switch($foo) {
        case 'a': return 1;
        case 'b': return 5;
    }
}

Do:

#!php
function checkStuff($foo) {
    $ret = null;
    switch($foo) {
        case 'a':
            $ret = 1;
        break;
        case 'b':
            $ret =2;
        break;
        default:
            throw new InvalidArgumentException('Expected a or b for $foo');
    }
    return $ret;
}

The exceptions I use most are InvalidArgumentException (for arguments I didn't expect), UnexpectedValueException (for return values of other functions I didn't expect) and LogicException (for a combination of circumstances that don't make sense). Whenever possible and suitable, I extend these to provide more information about the context of the exception.

#4 Use 0, null and false consistently

For example:

#!php
function getCount() {
    if(is_null($this->_count)) { 
        $this->_count = count($this->_property);
    }
    return $this->_count;
}

#3 Design by contract, design for extension

Try to extract interfaces for all your classes. Even if you don't explicitly use these interfaces, e.g. in type hints, next to being a good exercise it still serves the purpose of showing what parts of a class are specific to the classes (own) implementation and what parts aren't. In other words: it shows the reader what functionality of a class is actually there because it follows a contract defined by an interface, and what code is merely aiding the implementation; hence showing how the same interface could be used for other implementations.

Designing for extension means that you show what parts are actually meant to be extended. This does not mean that you should declare everything protected or public. It means that you should think hard about what entry points a derived class should use, and where it is supposed to hook into base functionality. Think about what method should be final and what properties could possibly be private, so you're not ambiguous about where you'd want a derived class to hook in:

Don't:

#!php
// We have 3 extension points
class Foo {
    // #1 (could be set by a derived constructor)
    protected $_thingy = null; 

    // #2 (could be overridden by a derived class)
    function getThingy() {
        if(is_null($this->_thingy)) {
            $this->_thingy = $this->_loadThingy();
        }
        return $this->_thingy;
    }

    // #3 (could be overriden by a derived class)
    protected function _loadThingy() {
        return "some value that relies on some heavy resources";
    }
}

Do:

#!php
// now we have only one. It is obvious the we 
// want _loadThingy to be the extension point
class Foo {
    private $_thingy = null;

    final function getThingy () {
        if(is_null($this->_thingy)) {
            $this->_thingy = $this->_loadThingy();
        }
        return $this->_thingy;
    }

    protected function _loadThingy() {
        return "some value that relies on some heavy resources";
    }
}

It goes without saying that if you don't intend your class to be extended at all, you should declare it final too.

Last but not least: always define a base constructor, even if it does nothing. This way, a derived class has no reason not to call the base constructor (which imho a derived class always should), so you won't break compatibility if you add initialization code to the base constructor later.

#2 Make your symbols explain themselves

I have the following few rules of thumb I use for naming methods and variables which I (try to) use consistently throughout my code.

#1 Only comment on code that needs clarification

Following #2, it does not make sense to comment on stuff that the code itself already explains. If you have code with clear symbol names, commenting is simply redundant most of the times. On the other hand, if you feel a piece of code needs more explanation, check to see if it could be solved with better naming of your symbols. In many cases, the code could easily transformed in a more readable equivalent, just by renaming some variables.

Don't

#!php
// Check if $c is the last one
if ($c == $l) {
    // print the table footer
    echo $tfoot;
}

Do

#!php
if($current == $last) {
    echo $htmlTableFooter;
}

And most of all, don't repeat your code in your comments, as I saw some time back: #!php // get page $page = getPage();

// assign page to template
$smarty->assign('page', $page);

Right. :P

If you still feel your code needs commenting, comment on a block of code, not on a per line basis. Each line should clarify itself by the way it is written, so you just need to explain the overall purpose of a piece of code. In many cases, though, the code should be in a separate function already, and you would comment on the inner workings of the function by using doccomments, not inline comments.

However, if an algorithm is complex enough to be commented on, try to comment in such a way that the comment is readable without the code. In other words, the comment is actually telling a story on "what is happening", so you needn't read the code to know what it does. This works best if all the code is commented on, not just some parts that aren't clear to the bare eye.

Read the original post & comments at drm.tweakblogs.net.


< Return to main page


You're looking at a very minimalistic archived version of this website. I wish to preserve to content, but I no longer wish to maintain Wordpress, nor handle the abuse that comes with that.