Small Function Headers

As opposed to MassiveFunctionHeaders.


An example of a small function header:

 //////////////
 // This function trims right and left of the string
 //////////////
 function trim(s)
 {
....
 }
If you want to add comment for parameters, you can add extra lines, starting with //.

This way, you don't waste space for useless function/method header, but at the same time while fast scrolling down the code, your eyes can catch separations between functions. -- SavasAlparslan

What's with the useless tear lines? Why not just write

 // This function trims right and left of the string
 function trim(s)
of course, it doesn't say what it trims... (more realistically it would read function trim( string s ))

Why not drop the header altogether? You don't need to document that this is a function, that is apparent. If the trim right and left information is what is important, rename the function to something like trimLeftAndRight() or extractMiddle(). The function name should be self-explanatory and not need a definition where the caller is not likely to see it. -- WayneMack

little note: "useless tear lines" are just for catching the beginning of a function while fast scrolling the source code. -- SavasAlparslan

I find that as my functions get shorter and more numerous, the tear lines just get in the way. Assuming you left justify the function name and indent the function body, the tear line visual clue is already there. -- WayneMack

SyntaxHighlighting can also do a good job of delineating function boundaries. Most editors will highlight the function defining words, so functions immediately stand out. This doesn't require visual space or typing, and stands out even more than comments (which are also usually colored). -- JonathanTang


Function headers are not evil or wrong. They're just a sign that the code may not be clear enough. -- WayneConrad


An approach presented to me by a previous co-worker:

 // -----------------
 // function trimSides
 // input: theString = string
 // returns: middle portion of theString, using the Wiki2001StringAlgorithmNotByKnuth
 // assumptions: theString must be at least 3 characters long (plus terminating null)
 //  to avoid buffer overflow error.  NotKnuthStringLibrary must be opened & initialized 
 //  before calling this function.
 // ------------------
 function trimSides(char* theString) {

The key addition here is defining the ASSUMPTIONS or RUNTIME REQUIREMENTS that are not obvious from the code itself as well as relevant references.


Why not make those assumtions and runtime requirements obvious from the code instead? You could either use asserts or you could check for the relevant conditions directly and throw an appropriate exception if they are violated.

 void trim(String theString) {
assert theString != null;
assert theString.length >= 2;
assert NotKnuthStringLibrary.isInitialized();
...
-- FalkBruegmann (hoping to work in a language with full support for DBC one day)

Great idea, Falk! Unfortunately the language selected by the GoldOwner does not support assertions, so the comments were needed to explain what can't be put in the code. See someone else's hypothetical reasons below.

Would rolling your own assertions help? In the simplest case, its just a couple of static methods in some utility class, after all. Restrictions are, such preconditions are not inherited (Java doesn't do this either), and you have to care about switching them on/off yourself. -- fb


Response:

  1. The NotKnuthStringLibrary? was very cleverly named "k2104b" by its creator, and we don't have the source. So we can't call it the "NotKnuthStringLibrary?" without comments or some hack.
  2. The library doesn't support an 'isInitialized()' function. Its behavior is simply "undefined" if you fail to follow the protocol.
  3. What about the "Wiki2001StringAlgorithmNotByKnuth" algorithm comment?
  4. True, a more descriptive function name would probably be helpful -- like trimLeadingAndTrailingSpaces maybe.

This response 1-3 doesn't obviate the need for SmallFunctionHeaders in the normative case. Only in the abnormal cases where you are left hanging.

Possible solution for 1.: Write a wrapper class / layer for k2104b with an Interface suitable to your needs, including isInitialized; alternatively, put the isInitialized logic into your own class responsible for initialization, and check it there (ForeignMethod). May not be worth the trouble, though.

Ad 3.: Right, which specific algorithm is used should not be part of the contract. If you are interested in specific properties of that algorithm, would it be possible to put those into preconditions?

BTW, I'm not criticizing SmallFunctionHeaders - just trying to get them even smaller ;-) -- FalkBruegmann


My favorite example of header comment reduction is in ObjectOrientedSoftwareConstruction 2. The example starts life as ...

  tangent_from (p: POINT): LINE is
           -- Return the tangent line to the current circle going through the point p,
           -- if the point is outside the current circle.
     require
        outside_circle: not has(p)
Meyer relentlessly removes the cruft from the comment.

The streamlined version of the function is now ...

  tangent_from (p: POINT): LINE is
           -- Tangent from p
     require
        outside_circle: not has(p)
Given that the comment merely repeats the name of the function, I'm surprized he left the comment in at all.

tangent_from (point_on_circle: POINT): LINE is -- mainly because I view single letter variables as a CodeSmell, with the possible exception of loops (and that is due to programmer familiarity). And I agree, the comment no longer has any value, although perhaps it is an intentional way of drawing that fact to the readers attention.

Interesting that the previous paragraph shows that it's author didn't understand either the original comment or the code (which both said that the point p is 'outside' the circle, rather than 'on' it). That shows how right his approach was: he would probably not have misunderstood.

 tangent_from (point_outside_circle: POINT): LINE is
Of course, that may just be that non-eiffelers are not used to looking for the 'require' element as part of the signature of a method.

For any circle and point not on the circle, there are 2 (two), not 1, possible LINEs which are tangent to the circle and pass through the point. Which does the function return?


Can't we just generate JavaDoc comments from code?

Assuming you are using JavaLanguage

JavaDoc works for many other languages with similar syntax to JavaLanguage. I've seen it used to good effect with PHP. I suspect it'd also work with CeeLanguage/CeePlusPlus and other C-syntaxed languages. -- JonathanTang


Low Level Design in File and Function Document Blocks

The design calls out all of the publically-available interface elements of a component and specifies the data that needs to change hands for the component to do its work. The internal functions of the component, the algorithms involved, and the internal data being exchanged are described in the document blocks for the files and functions that make up the component.

If there is insufficient detail in the design to allow such documentation to take place then the design needs to be refined to address additional detail at the lower level. This process is iterative and not necessarily entirely predictive. This is acceptable; it is rare that a planned implementation will follow its original path without any mid-course corrections during discovery, testing, feature tuning, etc.

The use of document blocks to provide the lowest level of design documentation is the best compromise solution between having insufficient design documentation and having too much inertia and rigidity in the design. It is up to the skill, experience, and good sense of the design/coding team to determine the proper cutoff point between design and coding, just as it was between the architecture and design. As with any layered plan, the design should identify more of the what (higher level) and code more of the how (lower level).

The actual code must closely follow the algorithm or pseudo-code logic expressed in the file or function document block. If the code needs to change then the document block must be updated to reflect the changes. It is not at all hard to keep these miniature “documents” up to date, since they are very terse and are co-located with the code in question.

Note that this is not the same as "self-documenting code" or any such silliness. The miniature document describes only what needs to be made plain when a cursory examination of the code itself can't do so. This description is created apart from and ahead of the composition of the code itself. It is a part of planning, vital to a successful coding endeavor.

Don’t get too hung up on the exact layout or format of a file or function document block. The idea is just to capture the important information in the most concise and easily-maintained form that can be had. Forcing compliance with some particular format or template leads to mostly empty and useless document blocks that not only don’t convey anything but also don’t get maintained. This is the sure way to kill any progress your team has made towards tracking the low level design.


CategoryDocumentation


EditText of this page (last edited April 7, 2013) or FindPage with title or text search