The Elements of Programming Style, 2nd Edition

Category: Computer Science
Author: Brian W. Kernighan, P. J. Plauger
4.4
All Stack Overflow 13
This Year Stack Overflow 1
This Month Stack Overflow 1

Comments

by mcguire   2018-03-30
I found it very interesting for being one of the very few books that deals with code as a formal system.

You don't need to understand the code; you don't need to step through the code. You follow the steps for "extract method" and you go from a working state to another working state with no worries.

The individual refactorings are more-or-less interesting, but as others have said, they're somewhat commonplace now.

Two books that every programmer should read: Software Tools[1] and The Elements of Programming Style[2] by Kernighan and Plauger. Bonus: The Unix Programming Environment[3] by Kernighan and Pike.

[1] https://www.amazon.com/Software-Tools-Pascal-Brian-Kernighan...

[2] https://www.amazon.com/Elements-Programming-Style-2nd/dp/007...

[3] https://www.amazon.com/Unix-Programming-Environment-Prentice...

by anonymous   2018-03-19

Facts:

1. GIT and other version controls systems treat white-space differently

Based on my experience, we faced on our projects: GIT and other version controls systems treat invisible spaces + TABS differently, and it leads to changes in lines, which actually haven't been affected. It's easy not to notice, when there will accidentally added one space + TAB = indent looks the same in IDE, but GIT will make the difference when merging. It damages your ability to effectively compare revisions in source control, which is really scary. It never going to happen when you are having spaces only.

2. Neutralize difference in collaborator's environment (Editor, OS, preferences, etc.)

The tab width (in spaces) depends on your environment (text editor, OS, preferences, etc.), but the space width is the same everywhere. IDEs are smart enough to treat white spaces up to you personal taste, but the output generated for collaboration should be up to standards.

3. Developers who use spaces make more money than those who use tabs

Using spaces instead of tabs is associated with an 8.6% higher salary. Using spaces instead of tabs is associated with as high a salary difference as an extra 2.4 years of experience. (source: Stack Overflow 2017 Developer Survey).

4. Numerous studies on coding style importance

If every collaborator on your project would keep the same standards on coding - it will be good in long run, collaboration is more efficient and professional, the same indent when you refactor or develop. Studies regarding that:

  1. For example, Ben Shneiderman confirmed this in Exploratory experiments in programmer behavior:

    when program statements were arranged in a sensible order, experts were able to remember them better than novices. When statements were shuffled, the experts' superiority was reduced.

  2. An old 1984 study by Soloway and Ehrlich cited in Code Complete, and supported studies from The Elements of Programming Style:

    Our empirical results put teeth into these rules: It is not merely a matter of aesthetics that programs should be written in a particular style. Rather there is a psychological basis for writing programs in a conventional manner: programmers have strong expectations that other programmers will follow these discourse rules. If the rules are violated, then the utility afforded by the expectations that programmers have built up over time is effectively nullified.

by anonymous   2017-08-20

In the example, the value 1.5F has an exact representation in IEEE 754 (and pretty much any other conceivable binary or decimal floating point representation), so the answer is almost certainly going to be yes. However, there is no guarantee, and there could be compilers which do not manage to achieve the result.

If you change the value to one without an exact binary representation, such as 5.1F, the result is far from guaranteed.

Way, way, way back in their excellent classic book "The Elements of Programming Style", Kernighan & Plauger said:

A wise programmer once said, "Floating point numbers are like sand piles; every time you move one, you lose a little sand and you pick up a little dirt". And after a few computations, things can get pretty dirty.

(It's one of two phrases in the book that I highlighted many years ago1.)

They also observe:

  • 10.0 times 0.1 is hardly ever 1.0.
  • Don't compare floating point numbers just for equality

Those observations were made in 1978 (for the second edition), but are still fundamentally valid today.

If the question is viewed at its most extremely restricted scope, you may be OK. If the question is varied very much, you are more likely to be bitten than not, and you'll probably be bitten sooner rather later.


1 The other highlighted phrase is (minus bullets):

  • the subroutine call permits us to summarize the irregularities in the argument list [...]
  • [t]he subroutine itself summarizes the regularities of the code [...]
by anonymous   2017-08-20

Basically, because the decimal number 0.01 does not have an exact representation in binary floating point, so over time, adding the best approximation to 0.01 deviates from the answer you'd like.

This is basic property of (binary) floating point arithmetic and not peculiar to Perl. What Every Computer Scientist Should Know About Floating-Point Arithmetic is the standard reference, and you can find it very easily with a Google search.

See also: C compiler bug (floating point arithmetic) and no doubt a myriad other questions.


Kernighan & Plauger say, in their old but classic book "The Elements of Programming Style", that:

  • A wise old programmer once said "floating point numbers are like little piles of sand; every time you move one, you lose a little sand and gain a little dirt".

They also say:

  • 10 * 0.1 is hardly ever 1.0

Both sayings point out that floating point arithmetic is not precise.

Note that some modern CPUs (IBM PowerPC) have IEEE 754:2008 decimal floating point arithmetic built-in. If Perl used the correct types (it probably doesn't), then your calculation would be exact.

by anonymous   2017-08-20

Sometimes if you can't come up with a good function name it's an indication that the function doesn't have a nice, crisp focus and needs to be refactored. If it's a class method, perhaps the class needs refactoring too.

But it's well worth the trouble finding the best possible names, since it makes your code so much more understandable and usable.

Update: Many software engineering authors have talked about the importance of naming. Henry F. Ledgard's Programming Proverbs (1975) and Brian Kernighan and P.J. Plaugher's Elements of Programming Style (1978) were early ones and are still worth reading. Steve McConnell's wonderful Code Complete (2nd Edition, 2005) is a more recent example, devoting an entire chapter to the topic.

Elements of Programming Style was in part patterned on Strunk and White's Elements of Style, which actually has a surprising relevance. Their stress on making prose clear and extremely concise applies to our technical writing and comments (and naming), but I've always seen it as analogous to what we do when we refactor and improve our code.

by anonymous   2017-08-20

Your code seems to prompt for the patron name on each iteration of the loop, which is an unusual way of organizing things. It should probably prompt for the names outside the loop. You should also error check the calls to scanf():

if (scanf("%.31s", f_name) != 1)
    ...break...or otherwise handle I/O problem...

We might need to see how you create the list of patrons within the library. It could be that you are storing the names in the same space each time, or something similar.

Your function always returns 0; there is no point that. Either it should not return any value (void) so you don't have to check what it returns, or you should make it return a struct Patron * for the found patron, or NULL (0) if there is no matching patron.


Your structures seem to me unexpectedly deeply nested. This fragment of code compiles, but I've not spent the effort to populate the list. However, providing 5 structures is 2 or 3 levels deeper than I'd expect. It definitely complicates your life.

#include <stdio.h>
#include <string.h>

struct Name
{
    char first[32];
    char last[32];
};

struct Patron
{
    struct Name name;
};

struct Patron_node
{
    struct Patron *patron;
    struct Patron_node *next;
};

struct Patron_list
{
    struct Patron_node *node;
};

struct Library
{
    struct Patron_list patrons;
};

int findpatron(struct Library* lib1, struct Patron **p_ptr)
{
    char f_name[32], l_name[32];
    struct Patron_node *currNode = lib1->patrons.node;
    printf("Enter the first name of patron: \n");
    if (scanf("%.31s", f_name) != 1)
        return -1;
    printf("Enter the last name of patron: \n");
    if (scanf("%.31s", l_name) != 1)
        return -1;
    while (currNode != NULL)
    {
        if (strcmp(currNode->patron->name.first, f_name) == 0 &&
            strcmp(currNode->patron->name.last,  l_name) == 0)
        {
            *p_ptr = currNode->patron;
            break;
        }
        currNode = currNode->next;
    }
    return 0;
}

I note I had to invent two structure names since they were not shown in your source code. At some point, you should look up the Law of Demeter and work out how to avoid violating it quite so flagrantly in your code.

For example:

int name_cmp_by_first(const struct Name *n1, const struct Name *n2)
{
    int rc = strcmp(n1->first, n2->first);
    if (rc == 0)
        rc = strcmp(n1->last, n2->last);
    return rc;
}

For the purpose at hand, it doesn't matter whether you compare first names first or last names first. In general, if you want to sort the data (for example), you would need to know which way to order them. I'm using the case-sensitive search you used; again, you might want to think about using a case-insensitive search instead. You can do that more easily when your comparisons are nicely separated and isolated as shown here.

int prompt_for_name(const char *prompt, char *name)
{
    printf("%s", prompt);
    if (scanf("%.31s", name) == 1)
        return 0;
    return -1;
}

int name_read(struct Name *name)
{
    if (prompt_for_name("first", name->first) == 0 &&
        prompt_for_name("last",  name->last)  == 0)
        return 0;
    return -1;
}

Notice that this avoids repetition in your code. Kernighan and Plauger summarized it neatly in their book 'The Elements of Programming Style':

  • The subroutine call permits us to summarize the irregularities in the argument list, where we can see quickly what is going on.
  • The subroutine itself summarizes the regularities of the code, so repeated patterns need not be used.

Then in your 'find_patron()` function:

struct Name to_be_found;
if (name_read(&to_be_found) != 0)
    return -1;  // Error return from function

while ...

    if (name_cmp_by_first(&name_to_be_found, currNode->patron->name) == 0)
        ...found the patron...

This is better; not fully clean, but better. A more realistic function would take in the name of the patron to be found and would search for that name in the list; you don't mix I/O operations such as reading the patrons name with searching operations. That is mixing up two very different operations.