"Design refers to the human endeavor of shaping objects to purpose."

   -- D. N. Perkins, "Knowledge as Design"






"The essence of design is to balance competing goals and constraints."

   -- Kernighan & Pike, "The Practice of Programming"






"KISS: Keep It Simple S_____"

   -- unattributed















--------------------------------------------------


Eric S. Raymond describes "The Hacker Attitude":

   1. The world is full of fascinating problems waiting to be solved.

   2. No problem should ever have to be solved twice.

   3. Boredom and drudgery are evil.

   4. Freedom is good.

   5. Attitude is no substitute for competence.


( http://tuxedo.org/~esr/faqs/hacker-howto.html )

















--------------------------------------------------
--------------------------------------------------

--------
Pointers
--------

Dangling pointers:

      T * ptr = new T();

      ...

      delete ptr;

      ...

      ptr->...;     // oh no !

      ...

      delete ptr;   // double deletion !



   - Why is this bad ?

   - How to avoid:

      delete ptr;   ptr = 0;


   - Why is it better to dereference a null pointer than
     a dangling pointer ?

      - in the former case, you are guaranteed to be
        stopped dead in your tracks (e.g. SIGSEGV),
        unless you're running DOS




Allocation/deallocation:

   - should happen at the same level (or layer)

   - e.g., this is bad:

      class String {
         char * s;
         ...
      public:
         ...

         // returns a copy of the string
         char * getCopy() const {
            char * copy = new char[ strlen(s)+1 ];
            strcpy( copy, s );
            return s;
         }
      };


   - Why is this bad ?

       - Client doesn't know if memory was allocated with malloc() or new

       - Client may not realize they have to deallocate


   - When client is obliged to allocate first, client can choose
     the method of allocation, and it is (implicitly) clear
     who should deallocate.

       - How can client know how much to allocate ?

       - What if it's really difficult/inefficient for the client to
         find out ahead of time ?

          - Then the client can pass in an object that wraps around
            the data to be returned, such as an STL container object

          - Another tactic is to use smart pointers (objects that
            transparently wrap around a dynamically allocated object,
            and that use reference counting to automatically detect
            when to delete).  Then it doesn't matter who "owns"
            the object.
     





--------------------------------------------------

------------
Optimization
------------

Let your optimizer do your dirty work for you.
It's better to keep your code readable.

- Don't

     int array[10];
     ...
     int * p = array;
     for ( int i = 0; i < 10; ++i ) {
        *p = ...;
        ++p;
     }

  Instead, do

     int array[10];
     ...
     for ( int i = 0; i < 10; ++i ) {
        p[i] = ...;
     }

- Don't

     x >> 1

  Instead, do

     x / 2

  (Exception: if it's more natural to think in terms of bit
  shifting, do a bit shift.)

- Don't

     x & 7

  Instead, do

     x % 8

  (Exception: if it's more natural to think in terms of
  bit ANDing, do the former.)



On the other hand, there are certain optimizations
that should be fairly obvious to do, and that don't
affect legibility.  For example,

- Don't

      if ( ptr == NULL && ... )
         ...
      else if ( ptr == NULL && ... )
         ...

  Instead, do

      if ( ptr == NULL ) {
         if ( ... )
            ...
         else
            ...
      }






--------------------------------------------------
--------------------------------------------------

-----------
Refactoring
-----------

- goal: minimize (hidden) dependancies between different parts of code
  and to centralize definitions of code/data/comments

- means the final line count of your code is smaller
  (but you may have to work more to get there)

- makes your code more maintainable
  (i.e. easier to modify in the future)
  and easier to understand

- can rarely be done right the first time (except in simple cases)

    - iterative process, requires work

    - ultimately, the programmer discovers the simplest
      set of primitives necessary to accomplish a task




Refactoring can and should be done with

   - code

   - constants (data)

   - comments

   - anything else you can think of









--------------------------------------------------



Extreme Programming (XP) principles tell us to 

   "Refactor mercilessly to keep the design simple as you go
    and to avoid needless clutter and complexity"


   "[...] always do the simplest thing that could possibly work."













The Camel book (Programming Perl) tells us that

   The three principle virtues of a programmer
   are Laziness, Impatience, and Hubris.














--------------------------------------------------
--------------------------------------------------

Source code as literature

- Most time spent in software development is not spent writing
  new code, but rather debugging and modifying existing code.

- It's easier to write code than to read it  :(
  (Joel Spolsky)

- Program source code should be designed for human consumption first.
  The computer's need to interpret and execute code is secondary.

- Literate programming (Knuth)
  - "A methodology that combines a programming language with
    a documentation language, thereby making programs more robust,
    more portable, more easily maintained, and arguably more fun to
    write than programs that are written only in a high-level language.
    The main idea is to treat a program as a piece of literature,
    addressed to human beings rather than to a computer."



















--------------------------------------------------
Identifier names - provide implicit documentation

Often, a well chosen variable or function name makes fewer comments necessary.

   This is good:

      class X {
         public:
            // Construct me with priority p and description d.
            X( const int p, const char* d );
            ...
      }


   But this is better:

      class X {
         public:
            X( const int priority, const char* description );
            ...
      }












--------------------------------------------------

--------
Comments
--------

- too few are bad

- too many are bad

   - duplicate comments

      - Why are they bad ?

   - redundant comments

      - You should assume the reader knows the syntax of the language.

- comments should be in the right place

   - In the header file:
      - brief comments on data members of a class
      - stuff that clients of your module need to know
        (e.g. what each public method of a class does,
        any special requirements or assumptions made about
        input, and policies for error reporting)

   - In the .cpp file
      - Comments specific to the implementation or
        private portions of the module

   - Just like variables, comments should be "scoped" to be close
     to the code they talk about.

- should not introduce more dependancies than necessary
  for the programmer to manage

   - avoid referring to variable names in comments.

   - try to make it easier for other programmers to modify your code
     and see immediately what comments they have to update



Example from 1st assignment:

   


   What's wrong with the above code ?

      - comments are too long and too decoupled from the
        corresponding code

      - comments could easily get out-of-sync with the code


Another example from 1st assignment:
      /*
         formUnionWith()
         The function first checks to make sure that the set to be
         unioned with is not empty.  If it is empty, no further
         operation is necessary. [...]
      */
      void Set::formUnionWith( const Set & set ) {
         int nMember = set.getSize();
         if ( nMember == 0 )
            return;
         ...
      }


   This is better:

      void Set::formUnionWith( const Set & set ) {
         if ( set.getSize() == 0 ) {
            // Set to be unioned with is empty; there's nothing to do.
            return;
         }
         ...
      }



Another example from 1st assignment:

      ////////////////////////////////////////////////////
      // First checks to make sure the list is not empty,
      // then makes sure that the element is there to delete.
      ////////////////////////////////////////////////////
      if ( !isEmpty() && NodeSearch(element, formerNode) ) {
         ...
      }

   This is better:

      if ( !isEmpty() && NodeSearch(element, formerNode) ) {
         ...
      }
      else {
         // The node isn't there to delete.
      }













General principle:

   Strive for economy of expression, in both code *and* comments !























--------------------------------------------------

-----------
Correctness
-----------


Defensive programming

   Be conservative.  When in doubt, assume whatever is safest.

   (This applies both to communicating with clients and
   when acting as a client ourself.)

   - e.g. can routine foo() return a null pointer ?

   - e.g. if read() returns EOF, is it still legal to call read()
     again on the same file, or will my program crash ?



Write code to test your own code (i.e. to perform "sanity checks").

   Example: if you write a sorted list class, test to make sure it's
       sorted after every insertion and deletion.





Assertions

   - help to detect and pinpoint bugs, often long before a human tester
     notices anything is wrong

   - provide implicit way of documenting your code,
     and of (partially) confirming (at run time!) assumptions made by the coder

   - can be easily removed with a compiler switch for optimized speed

   - not good as a general error-handling facility.
     Should only be used to detect bugs that are internal to a system.

     If an external client (including a human user) misbehaves
     or does something illegal, they should be informed in
     some way, so that they can handle (or ignore) the error as they choose.
     Meanwhile, the internal system should recover from the error
     as gracefully as possible.

     (In general, errors due to external conditions should be detected
     at a low level and handled at a high level.)





Design your code to make errors as obvious as possible
to a human inspector.  Avoid weirdness like this:

   T* ptr = new T[ N ];

   for ( int i = 0; i < N; ++i )
      foo( *ptr++ );

   delete [] ( ptr - N );





















--------------------------------------------------
--------------------------------------------------

------------------------
Design vs Implementation
------------------------

- The cost of changing a design increases very fast
  as you progress into the implementation stages.
  Try to get a complete design done early
  (if not of the entire implementation, then at least
  of the modules and interfaces between them).




- On the other hand, John Cage tells us that not knowing
  where to start is a common form of paralysis.
  His advice: start anywhere.




- If you adopt Cage's advice, when your start writing a
  complex piece of code, start with the easy stuff first.
  Leave complicated, special cases for last.













--------------------------------------------------
--------------------------------------------------

-----------
Your Health
-----------


- Beware of RSI !   (repetitive strain injury)

   - it's for real

   - it really sucks





























--------------------------------------------------
--------------------------------------------------













"La perfection est atteinte non quand il ne reste rien a ajouter,
mais quand il ne reste rien a enlever."
   -- Antoine de Saint-Exupery
      (author of Le Petit Prince)







The above serves as a good litmus test for
elegance in design.