[Asterisk-Dev] [Rant] [long] - code style and quality
Luigi Rizzo
rizzo at icir.org
Fri May 6 15:54:42 MST 2005
I am new to the project, but i find the coding style used in many
of the components of asterisk very hard to follow and maintain. The
examples i supply below are not to point fingers but just to put
facts behind my claims.
The request I make, which I hope you will agree with after reading
the rest of this email, is that developers should be more careful
about code quality, and should actively try to fix things as they
encounter functions that are more than 100 lines or code that
looks like cut&pasted from other parts.
These changes are not gratuitous, they help revealing potential
bugs, and make the program enormously more maintainable.
List with examples follows, in case someone agrees feel free to
put this stuff in the 'coding style' file
cheers
luigi
LIST OF COMMON STYLE BUGS
1. in many cases we have blocks of this kind:
if (condition) {
bla bla bla...
... 100+ lines of code ...
} else {
ast_log(LOG_WARNING, "too bad, can't do that");
return -1;
}
see e.g. apps/app_voicemail.c function sendmail() or its clone
sendpage(), channels/chan_sip.c:sip_new(),
channels/chan_sip.c:build_peer(), and more.
Also similar (not ending with a return but a break;)
channels/chan_zap.c:zt_handle_event() in e.g.
case ZT_EVENT_WINKFLASH:
case SIG_FXOKS:
and so on.
The more readable way to write these statements is to put
the very short block first.
if (!condition) {
ast_log(LOG_WARNING, "too bad, can't do that");
return -1;
}
bla bla bla...
... 100+ lines of code ...
The return/break, when present, even saves the need for an extra
level of indentation making This way one immediately sees the
simple case, and can focus on the complex one without having to
remember that there might be an alternate path of unknown
complexity.
2. Huge source files and functions.
Several source files are between 4k and 10k+ lines of code.
A large number of functions are 300-700+ lines long.
E.g. channels/chan_zap.c:zt_handle_event() at 700 lines,
channels/chan_zap.c:zt_read() about 350,
pbx.c:ast_pbx_run() 330 lines,
apps/app_voicemail.c:load_config() about 370,
apps/app_voicemail.c:vm_execmain() almost 400, and so on
This is way beyond good programming practice.
I cannot believe that they couldn't be made shorter or broken up
in subfunctions to make them more readable.
Part of the problem is #3, #4, and #5 below, but a lot comes
from carelessly adding code to the body of functions without
considering readability.
3. Unused language features.
The C language has the conditional expression operator, and there
is no reason not to use it when it can reduce the amount of
programming errors and ease readability.
A sample from zt_read():
if (ps.rxisoffhook)
{
p->subs[index].f.subclass = AST_CONTROL_RADIO_KEY;
}
else
{
p->subs[index].f.subclass = AST_CONTROL_RADIO_UNKEY;
}
could be written as
p->subs[index].f.subclass = ps.rxisoffhook ?
AST_CONTROL_RADIO_KEY : AST_CONTROL_RADIO_UNKEY
and not just because it is a lot shorter, but because when someone
reads the code immediately understand that the test affects only
the right hand side of the expression, not the left hand side.
And because by writing the code this way the programmer does not
have a chance to write, by mistake, the LHS in two different ways.
4. No use of functions for similar blocks of code
Also from zt_read line 4093:
p->subs[index].f.frametype = AST_FRAME_NULL;
p->subs[index].f.datalen = 0;
p->subs[index].f.samples = 0;
p->subs[index].f.mallocd = 0;
p->subs[index].f.offset = 0;
p->subs[index].f.subclass = 0;
p->subs[index].f.delivery.tv_sec = 0;
p->subs[index].f.delivery.tv_usec = 0;
p->subs[index].f.src = "zt_read";
p->subs[index].f.data = NULL;
and repeated almost completely in the same function at line 4295
(with some fields forgotten, i believe).
And in the same file, lines 3261 and 3967.
Clearly, this would deserve a frame_init() function, if not already there.
5. Abuse of cut&paste programming
Examples:
apps/app_voicemail.c functions sendmail() and sendpage()
have probably 60 lines in common (in addition to issues #1 and #3
mentioned above) clearly pasted from one place to another.
channel.c:ast_channel_walk_locked() has a block of 12 lines
if (ast_mutex_trylock(&l->lock)) {
...
}
replicated twice. The second instance is completely useless, as
by code inspection you realize that you can jump to the first one.
channel.c:ast_safe_sleep(x, y) is exactly the same as
ast_safe_sleep_conditional(x, y, NULL, NULL)
it does not make sense to rewrite the body - the optimization
in avoiding the test in the loop (see also #6 below) is negligible
compared to the overhead of the subsequent ast_waitfor() which
calls ast_waitfor_n() which calls ast_waitfor_nandfds() which
finally calls the system call poll().
6. Abuse of micro optimizations
In the current state of the code, micro optimizations to save a
handful of clock cycles for function call overhead is pointless
especially when it causes code bloat or renders the source less
readable or portable.
Example #1:
pbx.c has a long and obscure macro, EXTENSION_MATCH_CORE, which is
called only twice by two very similar functions. It would be
trivial to merge them (perhaps using wrappers to differentiate)
and get rid of the macro.
Example #2:
ast_copy_string() ends with a __builtin_expect() to check if it
is necessary to decrement dst to write the terminator. Do you really
believe the hint to the compiler (because this is what it does)
helps a lot after the function call overhead ? If anything,
one should optimize the loop perhaps by relying on the os/compiler
library to do the strncpy in an efficient way eg. a word at
a time...
More information about the asterisk-dev
mailing list