The Practice of Programming by Rob Pike, Brian Kernighan

simplicity, short and manageable
clarity, easy to understand
generality, works well in a broad range of situations and adapts to new ones
automation, let the machine do the work for you

Use descriptive names for globals, short names for locals.
Be consistent. Give related things related names that show their relationship 
and highlight their difference.
Use active names for functions. Function names should be based on active verbs, 
perhaps followed by nouns.
Be accurate. A name not only labels, it conveys information to the reader. A 
misleading name can result in mystifying bugs.

Expressions And Statements
Write expressions and statements in a way that makes their meaning as 
transparent as possible. Write the clearest code that does the job. Use spaces 
around operators to suggest grouping; more generally, format to help 
Indent to show structure.
Use the natural form for expressions. Write expressions as you might speak them.
Parenthesize to resolve ambiguity. Parenthesis specify grouping and can be used 
to make the intent clear even when they are not required.
Break up complex expressions.
Be clear. Write clear code, not clever code.
Be careful with side effects: besides returning a value, they also modify an 
underlying value.

Consistency And Idioms
If the same computation is done the same way every time it appears, any 
variation suggests a genuine difference, one worth noting.
Use a consistent indentation and brace style.
Use idioms for consistency. Like natural languages, programming languages have 
idioms, conventional ways that experienced programmers write common pieces of 
Use else-ifs for multiway decisions. Align vertically. Avoid nested ifs.

Function Macros
Avoid function macros. Parenthesize the macro body and argument.

Magic Numbers
Magic numbers are the constants, array sizes, character positions, conversion 
factors, and other literal numeric values that appear in a program.
Give names to magic numbers.
Define numbers as constants, not macros.
Use character constants, not integers.
Use the language to calculate the size of an object.

The best comments aid the understanding of a program by briefly pointing out 
salient details or by providing a larger-scale view of the proceedings.
Don't belabor the obvious.
Comment functions and global data.
Don't comment bad code, rewrite it.
Don't contradict the code.
Clarify, don't confuse.

Why Bother?
Main concerns of programming style:
 descriptive names
 clarity in expressions
 straightforward control flow
 readability of code and comments
 consistent use of conventions and idioms
Good style should be a matter of habit.

Algorithms And Data Structures
Only a handful of basic algorithms show up in almost every programing - 
primarily searching and sorting.
Almost every data structure is derived from a few fundamental ones.

Nothing beats an array for storing static tabular data.
Sequential/Linear search is fast enough when the amount of data is small.
Binary search is fast enough when the amount of data is larger and it is sorted.

If repeated searches are going to be made in some data set, it will be 
profitable to sort once and then use binary sort.
Quick sort was invented by C. A. R. Hoare in 1960. It works by partitioning and 
array into little and big parts:
 Pick one element of the array (the "pivot").
 Partition the other elements into two groups:
  "little ones" that are less than the pivot value, and
  "big ones" that are great than or equal to the pivot value.
 Recursively sort each group.

Growing Arrays
Growing a sorted array by inserting n elements one at a time is an O n ^ 2 
operation that should be avoided if n is large.
Arrays are sometimes appropriate when working with a variable yet small number 
of things, in which case arrays should be resized in chunks.

A singly-linked list is a set of items, each with data and a pointer to the 
next item. The head of a list is a pointer to the first item and the end is 
marked by a null pointer.
Arrays have fixed size, but a list is always exactly the size it needs to be to 
hold its contents, plus some per-item storage overhead to hold the pointers.
Lists can be rearranged by exchanging a few pointers and is cheaper than the 
block move necessary in an array.
When items are inserted or deleted from a list the other items aren't moved.
Fundamental list operations: add a new item to the front or back, find a 
specific item, add a new item before or after a specific item, perhaps delete 
an item.

A tree is a hierarchical data structure that stores a set of items in which 
each item has a value, may point to zero or more others, and is pointed to by 
exactly one other. The root of the tree is the sole exception: no item points 
to it.

Hash Tables
A hash table is a combination of arrays, lists, and math.
A typical application is a symbol table, which associates some value with each 
member of a dynamic set of strings.
The idea is to pass the key through a hash function to generate a hash value 
that will be evenly distributed through a modest-sized integer range. This hash 
value is used to index a table where the information is stored.

Summary Of Algorithms And Data Structures
Most programs are based largely on arrays, lists, trees, and hash tables.

Design And Implementation Lessons
Choose simple algorithms and data structures.
Start detailed design with data structures, often the algorithms follow 
It's hard to design a program completely then build it: constructing real 
programs involves iteration and experimentation.
Start with something simple and evolve it as experience dictates.

The essence of design is to balance competing goals and constraints.
Among the issues to be worked out in design are
 Interfaces: what services and access are provided? The interface is, in 
effect, a contract between supplier and customer. The idea is to provide 
services that are uniform and convenient, with enough functionality to be easy 
to use but not so much as to become unwieldy.
 Information Hiding: What information is visible and what is private? an 
interface must provide straightforward access to the components while hiding 
the details of the implementation so they can be changed without affecting 
 Resource Management: Who is responsible for managing memory and other limited 
resources? Here the main problems are allocating and freeing storage, and 
managing shared copies of information.
 Error Handling: Who detects errors, who reports them, and how? When an error 
is detected, what recovery is attempted.
Build an interface to throw away: a prototype.
Prototyping reveals unidentified design decisions.

Interface Principles
An interface is that detailed boundary between code that provides a service and 
the code that uses it.
To prosper an interface must be well suited for its task - simple, general, 
regular, predictable, robust - and it must adapt gracefully as its users and 
implementation change.
Hide implementation details. the implementation of the interface should be 
hidden so that it can be changed without affecting or breaking anything.
Choose a small orthogonal set of primitives. An interface should provide as 
much functionality as necessary but no more, and the functions should not 
overlap excessively in their capabilities.
Do one thing, and do it well.
Don't reach behind the user's back. A library function should not write secret 
files and variables or change global data.
Do the same thing the same way everywhere. Consistency and regularity are 
Related things should be achieved by related means.

Resource Management
Free a resource at the same layer that allocated it. Have the same interface 
that allocates it reclaim it.
Write reentrant code, it works regardless of the number of simultaneous 

Abort, Retry, Fail
Detect errors at a low level, handle them at a high level.
Use exceptions only for exceptional situations.

Look for similar patterns.
Examine the most recent changes.
Don't make the same mistake twice.
Debug it now, not later.
Get a stack trace.
Read before typing.
Explain your code to someone else.
Make the bug reproducible.
Divide and conquer.
Study the numerology of failures.
Display output to localize your search.
Write self checking code.
Write a log file.
Draw a picture.
Use tools.
Keep records.

Test code at its boundaries.
Test pre and post conditions.
Use assertions.
Program defensively.
Check error returns.
Test incrementally.
Test simple parts first.
Know what output to expect.
Verify conservation properties.
Compare independent implementations.
Measure test coverage.
Automate regressions testing.
Create self-contained tests.

Automate timing measurements.
Use a profiler.
Concentrate on the hot spots.
Draw a picture.
Use a better algorithm or data structure.
Enable compiler optimizations.
Tune the code.
Don't optimize what doesn't matter.
Collect common subexpressions.
Replace expensive operations by cheap ones.
Unroll or eliminate loops.
Cache frequently-used values.
Write a special-purpose allocator.
Buffer input and output.
Handle special cases separately.
Precompute results.
Use approximate values.
Rewrite in a lower level language.
Save space by using the smallest possible data type.
Don't store what you can easily recompute.

Stick to the standard.
Program in the main stream.
Beware of language trouble spots.
Try several compilers.
Use standard libraries.
Use only features available everywhere.
Avoid conditional compilation.
Localize system dependencies in separate files.
Hide system dependencies behind interfaces.
Use text for data exchange.
Use a fixed byte order for data exchange.
Change the name if you change the specification.
Maintain compatibility with existing programs and data.
Don't assume ASCII.
Don't assume English.