Self documenting code is code that you can read. You can follow variable and function names and know what a function does. A trend over the past two decades is to use self documenting code in lieu of comments. This approach is an algorithm to make code understandable, and like all algorithms it has its place and time. Unfortunately self documenting code fails.
The first failure of self documenting code is peer review. Reading through code tells you what it does, not what it is supposed to do. This makes peer review extremely time consuming. If something in a function seems unusual, you have to dig through code and see how it’s used before you can determine if it’s a problem or not. Consider this C example:
int hash_bucket(int hash) { return hash % 1000; }
// Return the bucket for a non-unique hash. // Given: The hash value, any integer // Returns: An integer between 0 and 999 int hash_bucket(int hash) { return hash % 1000; }
Peer reviewing the first function is very time consuming while the second is easy. In this case the function has a bug. If the hash is negative then it can return a negative number. A good developer will see this and switch to unsigned ints.
The next failure of self documenting code is that it’s harder to support. Reading through a hundred lines of code is a lot slower than reading a single sentence summary. If code is written once then never touched again then this doesn’t matter. For code that requires changes and support, comments save a lot of time. At the minimum, every file, class and function should have a comment block.
Self documenting code also fails for math intensive processes. Consider an algorithm used for ray tracing a scene containing transparencies and reflectance. Fresnel Equations provide equations for reflectance and Snell’s Law provides equations for refraction. These are impossible to write as self documenting code. An example of Fresnel Equations in C is:
// Calculate the reflectance percent of light, based on two angles and two // indexes of refraction // Assumes: unpolarized light // Source: Fresnel Equations, from Wikipedia double reflectance(double cos_ang_inc, double cos_ang_trans, double n1, double n2) { double r1 = (n1 * cos_ang_inc - n2 * cos_ang_trans) / (n1 * cos_ang_inc + n2 * cos_ang_trans); r1 *= r1; double r2 = (n1 * cos_ang_trans - n2 * cos_ang_inc) / (n1 * cos_ang_trans + n2 * cos_ang_inc); r2 *= r2; return .5f * (r1 + r2); }
Self documenting code also fails with complex algorithms. New programmers rarely use complex algorithms, so they don’t happen across this problem. The more skilled a programmer, the more complex algorithms they use. In an algorithm, even a simple one, the pattern is far more important than the variable or function names. An example of this in c is:
void copy(char *from_string, char *to_string) { int character_offset; for (character_offset = 0; from_string[character_offset] && from_string[character_offset] != '\n'; character_offset++) to_string[character_offset] = from_string[character_offset]; to_string[character_offset] = 0; }
void copy(char *from, char *to) { int i; for (i = 0; from[i] && from[i] != '\n'; i++) to[i] = from[i]; to[i] = 0; }
The pattern is far easier to see in the second function. A longer example written in PHP is: (click to expand)
// This function splits up a polygon and adds all new polygons to the BSP // Given: The plane to split private function split_polygon(polygon $plg) { // Step 1, Create a Circular Link List $start=new bsp_link_list($plg->vertices[0]); $old_item=$start; for ($a=1;$a<sizeof($plg->vertices);$a++) { $new_item=new bsp_link_list($plg->vertices[$a]); $old_item->next=$new_item; $new_item->prev=$old_item; $old_item=$new_item; } $old_item->next=$start; $start->prev=$old_item; // Step 2, Record the Side of Each Point $item=$start; do { $item->side=$this->polygon->side($item->point); } while (($item=$item->next)!=$start); // Step 3, Add Intersection Points // Reposition start to a point with a definate side while ($start->side==0) $start=$start->next; $item=$start; do { $a=$item->side; $b=$item->next->side; $c=$item->next->next->side; if ($a*$b<0) { // Intersection Point - Case 1 // We need to insert two intersection points between $item and $item->next $pt=$this->polygon->intersection_point($item->point,$item->next->point); $new_item_1=new bsp_link_list($pt,true); $new_item_2=new bsp_link_list($pt,true); $new_item_1->side=$a; $new_item_2->side=$b; $item->insert_after($new_item_1); $new_item_1->insert_after($new_item_2); $item=$new_item_2; } else if ($b==0) { if ($a*$c<0) { // Intersection Point - Case 2 // Insert a new intersection point $item->next->is_intersection_point=true; $new_item=new bsp_link_list($item->next->point,true); $item->next->side=$c; $new_item->side=$a; $item->insert_after($new_item); $item=$item->next; } else if ($c==0) { // Find the next non zero point for ($item2=$item->next->next;!$item2->side;$item2=$item2->next); if ($a!=$item2->side) { // Intersection Point - Case 3 // We mark both points as intersection points and set their side $item->next->side=$a; $item->next->is_intersection_point=true; $item2->prev->side=$item2->side; $item2->prev->is_intersection_point=true; } $item=$item2->prev; } } } while (($item=$item->next)!==$start); // Step 4, Link the Intersection Points // We start by calculate the intersection point positions. We use these to // help sort the intersection points into the correct order. $intersection_vector=vector_cross_vector($this->polygon->normal,$plg->normal); $intersection_vector->convert_to_unit(); $intersection_point=NULL; $item=$start; do { if ($item->is_intersection_point) { if (!$intersection_point) { $intersection_point=$item->point; $item->intersection_position=0; } else { // Record the relative position $v=new vector($intersection_point,$item->point); $item->intersection_position=vector_dot_vector($v,$intersection_vector); } } } while (($item=$item->next)!==$start); // Create intersection point lists, one for each side. $positive=array(); $negative=array(); $item=$start; do { if ($item->is_intersection_point) { if ($item->side>0) $positive[]=$item; else if ($item->side<0) $negative[]=$item; } } while (($item=$item->next)!=$start); // Sort the interesction point lists $this->sort_intersection_points($positive); $this->sort_intersection_points($negative); // Re-link the intersection points $this->link_intersection_points($positive); $this->link_intersection_points($negative); // Create the new polygons $this->add_intersection_polygons($positive,$plg); $this->add_intersection_polygons($negative,$plg); }
This is an example of a polygon splitting algorithm used in a binary space partition (BSP).
Code, no matter how readable, requires comments. In addition to comments in front of each function, complex functions can also benefit from comments within the code. Programmers tend to see comments as a waste of time but comments save time.