CSE 532 Spring 2008: Lab 3
Lab 3: "Interceptors and Service Reconfiguration"
Points: 25% of final grade
Submitted: by electronic submission to cse532@cec.wustl.edu
Due: by 11:59pm Sunday May 4, 2008
Purpose
The purpose of this assignment is to combine new ideas for
intercepting user-triggered events and reconfiguring services, with
the ideas from the previous labs of multi-threading, STL algorithms
and iterators, reactive concurrency, active and passive connection
establishment, and service handling.
In this assignment you will again use the basic wrapper facades for
networked OO programming provided by the ACE framework, that you used
in the previous labs. In this lab you will refactor and extend your lab 2
solution to provide the ability to reconfigure features of your client
and server programs dynamically at run-time through a simple textual
user interface. This will require you to think carefully about how
your client and server programs require and provide different kinds of
services, and how changes to those services can be made responsively,
but in a carefully controlled manner.
In addition to providing a thorough writeup of your solution for this
lab, and of how your lab designs and implementations have evolved
throughout the semester, this assignment will require you to implement
the following new capabilities in both your client and server
programs:
- make your client and server programs repetitive, so that once
a game has completed a new game of that same kind with that same name
is started, and the clients and servers continue to run in that way
until they are either terminated or reconfigured to play different games
(after which the games in progress will either end naturally or be
safely and consistently cancelled, at your discretion);
- add support for a third kind of poker game, called "hold 'em",
in which each player receives 2 cards of their own (called "hole cards"),
and all players share 5 common cards (called "table cards");
- control interfaces so that client and server services can be
reconfigured dynamically, and so that changes can be made to those
services safely while the client and server programs are running;
- a responsive textual user interface that accepts
reconfiguration commands and dispatches them to the appropriate parts
of your client and server programs, while the clients and servers are
running;
- interceptors at appropriate points in your client and server
programs so that reconfiguration events can be sent and detected in
order to trigger consistent changes to those programs along end-to-end
communication paths; and
- control mechanisms that when invoked by the interceptors enact
the appropriate changes to the services, and which use the services'
control interfaces to ensure changes are made safely while the clients
and servers are running.
Optionally, you will also have the opportunity to add features for
extra credit as described below, which include:
- the ability to
support remote reconfiguration through flooding of admin commands (as
noted above) so that reconfiguration can be done locally or remotely;
or
- adding timers to the lowest level event handling layers on the
server and the clients in order to vary their duty cycles according to
a (re)configuration command that should specify the on-interval and
off-interval of each duty cycle.
Assignment
All features that were required in lab 2 are again required in lab 3.
As in lab 2, each client again can be connected to multiple servers,
and each server can be connected to multiple clients. In this
assignment, features such as which poker games (stud, draw, hold 'em) a
client or server knows how to play, which games a client wants to play,
and where clients contact game servers, will be made configurable so that
they may be changed at run-time.
You will extend your lab 2 implementation to define a textual
configuration command language that expresses the different
configuration options for your client and server programs. You will
also implement a simple textual user interface (UI) that can read
those configuration commands from each client or server's standard
input (for example using the cin standard input
iostream), recognize the commands and their parameters, and invoke the
appropriate mechanisms to ensure the commands are carried out. You
will need to think about which thread(s) will manage this interface,
which could be done either reactively or (probably easier) via a
separate dedicated UI thread.
You will need to think about the different design dimensions of your
client and server programs, along the lines discussed in Chapter 2 of
the C++NPv2 textbook. This will help you decompose your design into
distinct "services" that can be reconfigured more or less
independently. For example, one idea may be to consider each game to
be a separate service, since each instance of a game type many be
configured independently of the others. Alternatively it may make more
sense to have the entire clients and servers be the reconfigurable
services since that is the level at which many of the reconfiguration
commands are targeted. Depending on how you have designed your client
and server programs, there may be other abstractions that can be
configured independently, and perhaps might even replace the game
instances as configurable services. Careful attention to this point
may lead to some initial refactoring of your lab 2 design to make
reconfiguration easier, which in the long run is likely to save more
time than it costs.
For the remote command portion of the extra credit, each host (client
or server) and each service you identify should be given a label, so
that configuration commands can be directed to it. How you structure
these labels is up to you, but one reasonable format for the labels
based on the assumption that the games are configurable services
would be host[:game] where "host" is the name given to
the client or server on the command line, and "game" is the name
given to the game on the command line.
Each of these distinct services should be given a configuration
control interface similar to the one discussed in the Component
Configurator lecture. You can apply these
configuration control interfaces to individual objects, or (possibly
by using the Facade or Extension Interface design patterns) to
collections of related objects, as appropriate to isolate services
from the effects of changes to other services, and to ensure changes
to services are carried out consistently and safely.
You will design (or obtain from ACE or another framework) mechanisms
to enact changes to services safely using those service control
interfaces. In particular, some thought may be required into
protocols for handshaking between configuration control mechanisms in
different parts of a program or between programs, to ensure the
changes are orchestrated safely, without risking deadlocks, race
conditions, or other synchronization and concurrency hazards.
Finally, you will design and deploy interceptors at key points in your
programs so that commands from the user and/or from other
configuration mechanisms and/or interceptors are detected and
dispatched appropriately (e.g., to local configuration
mechanisms or to other interceptors).
To do this please perform the following tasks:
Application Features
You should make your client and server programs repetitive, so that
once a game has completed a new game of that same kind with that same
name is started, and the clients and servers continue to run in that
way until they are terminated or reconfigured. For example, when a
server finishes a game it should immediately start a new edition of
the same type of game (and with the same name, but with a freshly
randomized complete deck of cards) and then wait for clients to join
that game; similarly, when a client finishes a game it should
re-connect to the same server for the same game name and type, etc.
You should also add support for a third kind of poker game, called
"hold 'em", in which each player receives 2 cards of their own (called
"hole cards"), and all players share the same 5 common cards (called
"table cards"), and then form hands consisting of their hole cards and
whichever 3 table cards make the highest ranking hand. As in the draw
and stud poker games, the player should be dealt their hole cards by
the server sending them to the players and removing them from the
deck. For the table cards the server should move them from the deck
into a separate data structure, and then communicate what they are to
each of the players in the game.
One other difference is that for each player the hand for scoring
should be first chosen by picking the highest ranking combination of 3
cards from the 5 table cards with that player's hole cards. Otherwise
(e.g., the content of the deck that is used, how the 5 cards chosen
for each player are scored, etc.) is similar to the stud and draw
poker games you have implemented in your previous labs.
Configuration Commands and Input Interpreter
Your client and server programs should recognize commands in a common
text-based configuration language with which the user of a program can
reconfigure any client or server on which the commands are issued (and
for extra credit, can be used to reconfigure any client or server that
is connected even transitively to that client or server). To do this,
you should develop a common command interpreter that: - reads a
line of text from the standard input stream,
- parses the line of
text until it recognizes a valid configuration command,
- executes
the command locally if appropriate, and
- (extra credit only) passes
the command along to any other client or server to which the command
pertains.
This interpreter should run concurrently with the other activities of
the client or server in which it is located. One straightforward way
to achieve this is to make the interpreter an active object, with its
own thread, and then add any synchronization mechanisms that are
needed to avoid race conditions or other hazards when the interpreter
thread interacts with other threads. Also, C++ iostreams and
(especially) stringstreams can be very useful for parsing textual
input (for a simple example of their use, please see the last few
CSE 332 lecture slides on data and I/O).
The command language you design should include the following
capabilities, though you are free to modify the syntax and/or
semantics of these commands as long as (1) you document the
differences thoroughly, and give examples of how each variation is to
be used, in your lab writeup; and (2) all of the capabilities given
here are still possible in the modified version. Note that much of the
following discussion assumes that entire clients and servers are identified as
distinct services, but if you choose a different service decomposition
then you should update the definitions of these commands appropriately
as needed.
The first set of commands is used to invoke and to configure features of the
system's reconfiguration capabilities, and how the clients and servers
interact with respect to them:
-
define <filename> - opens
a definition file for a client or server, which causes the
server or client to discard all previous definitions (e.g., if
your clients were started with a definition file as in lab 2,
a define command that names a different file would replace the
current definitions with a new one. If games based on the old
definition are in play you should decide whether to terminate
them or have them complete prior to discarding the old definitions,
as long as the new definitions can take place immediately.
For the servers, you should introduce a new kind of definition
file that indicates what types of games the server knows how to
play (code for all 3 should be available in all servers, it's just
that well use this feature to give the servers "selective amnesia"
about what their abilities are.
-
process <filename> - opens
the named file and parses each line of the file as a separate
configuration command which the parser issues to the configuration
framework. Note that files should be able to expand by
containing instances of this same command that results in
another file being opened - watch out for unbounded
recursion, however: one reasonable solution that allows
repetitive inclusion of a file but prevents unbounded
recursion would be to (1) close each file after it has been
fully parsed, (2) remember which files are currently open,
and (3) disallow them being re-opened until they have been
closed.
-
current [label:<label>] [key:<value>] -
prints out the current
configuration of the host or service identified by the label. If no
label is given, a reasonable default would be to print out the
configurations of all of the services on the host on which the command
was issued.
- (extra credit only)
admin <local | key:<value> | global>
[label:<label>] [key:<value>] -
changes the administration mode of the service (or if no
service is specified all services on the host) specified in
the label parameter (if no label is given, a reasonable
default would be to apply the command to all services on the
host on which the command was issued).
- If the administration mode for a service is set to
local, then only
commands issued directly on the host where the service resides
should be processed (for
any non-local commands, a suitable error message should be
printed but the host should accept subsequent local commands
while in that mode).
- If the administration mode is set to
key, then the service should
remember the key value that was supplied in the second
parameter to the admin command, and should only process
configuration commands that either are issued locally or are
accompanied by a matching key value (for any other commands, a
suitable error message should be printed but the host should
accept subsequent local or appropriately keyed commands while
in that mode).
- If the administration mode is set to
global, then any command issued
to the host should be processed.
Notice that the last parameter to the admin command is an optional key
parameter that can be used to authorize a remote command to change the
administration mode, if the host was already in a keyed administration
mode. A similar optional key paramter can be given for all other
commands where the service's administration mode matters.
The second set of commands is used to configure the games played
and how the clients and servers interact with respect to them:
-
play <stud | draw | holdem> -
tells a client or server that it knows how to play a kind of poker,
after which a client will respond to definition file lines for that
kind of game, or a server will respond to requests from players to play
that kind of game, from that point until subsequent termination or a
reconfiguration that turns off that ability.
-
dontplay <stud | draw | holdem> -
tells a client or server that it does not know how to play a kind of poker,
after which a client will ignore definition file lines for that
kind of game, or a server will refuse requests from clients to play that
kind of game, from that point until subsequent termination or a reconfiguration
that turns off that ability.
-
suspend [<game name>] - pauses
a client or server's handling of a game (or if no game name is
given, all games) temporarily until
a subsequent resume command for that game (or all games) is issued.
-
resume [<game name>] - resumes
a client or server's handling of a game (or if no game name is
given, all suspended games).
EXTRA CREDIT: A bonus of up to 5 points (scores above
100 are possible) will be given to any team that does one or both of
the following additions to their project:
- gives the ability to
support remote reconfiguration through flooding of labeled admin and
configuration commands (as noted above) so that reconfiguration can
be done locally or remotely;
or
- adds timers to the lowest level event handling layers on the
server and the clients in order to vary their duty cycles according to
an additional (re)configuration command that should specify the on-interval and
off-interval of each duty cycle.
Teams are encouraged to try either or both of these options, and to
document the designs and experiences with them in the lab writeup.
Full or partial credit of as much as 5 points total will be awarded
for any non-trivial efforts on these.
Service Interfaces
Each configurable service in your client and server implementations
should be able to be reconfigured after it has started running. To do
this safely, each service should provide a service interface which the
interceptors you will add to your client and server implementations
can use to initialize, terminate, suspend, or resume a service so that
it can be upgraded without encountering race conditions, deadlocks, or other
hazards. The following methods should be supported, per the Component Configurator
pattern:
-
init - tells a service to (re)initialize itself based on a definition file.
-
fini - tells a service to terminate itself.
-
suspend - tells a service to suspend itself.
-
resume - tells a service to resume itself.
-
info - querys a service about
its current configuration status plus any other relevant attributes
(such as its label, its administration mode, its key value if any, etc.).
The interceptors in your client and server programs (see next) should
use these interfaces to ensure that configuration changes are made in
a safe manner. One reasonable way to do this may be to define a
consistent sequence of interactions between any interceptor and any
service it reconfigures. In that case the nuances of each service's
ability to be upgraded while it is running can be encapsulated
entirely within its implementation of the above methods (including
perhaps making some of them do nothing), which may help to simplify
your interceptor implementations overall.
However, depending on how you have implemented your client and server
programs, it is also possible that for one or more interceptors a
different interaction sequence may be more appropriate between that
interceptor and one or more of the services it configures (for
example, to ensure safety or to keep the implementations of the
service interface methods above from becoming overly complex). In that
case you are free to vary how any interceptor interacts with the
service(s) it configures, as long as you justify that decision in your
lab writeup.
Interceptors and Service Repository
You should provide interceptors at appropriate places in your client
and server architectures so that commands can be entered on a client
or server (and for extra credit, forwarded to other clients and/or
servers as appropriate), and targeted to particular services (or
broadcast to all services) on a client or server, as appropriate.
One approach is to intercept configuration commands in-band, and apply
special handling as needed to prevent commands from being interpreted
as being application data (for example if commands are forwarded along
the normal data transmission paths between clients and servers). This approach
has the advantages that (1) the existing lab 2 infrastructure can be
re-used significantly, and (2) commands often can be intercepted and
dispatched efficiently within the handlers they affect, but the
disadvantage that (3) special processing (and possibly revisions to
the data transmission format) must be added to distinguish commands
from non-command data.
Another approach is to intercept configuration commands entirely
out-of-band in which case special purpose communication paths (e.g.,
built using new handlers, sockets, etc.) should be added in order to
separate the command-related traffic from the data traffic. This
approach has the advantage of (1) distinguishing the two kinds of
traffic explicitly, but has the disadvantage that (2) new acceptors,
connectors, and service handlers need to be provisioned for the
command handling paths, along with the necessary concurrency and
synchronization mechanisms to integrate them safely with the existing
data management infrastructure from lab 2.
A third approach is to combine the out-of-band approach with the
in-band approach and selectively distinguish data traffic from command
traffic only at key points in the architecture where the disadvantages
of doing so are less, and/or the advantages of doing so are greater.
In either case, please think carefully about the trade-offs in safety,
performance, and complexity, and please write up your decisions as
part of your expanded README file, per the discussion of the writeup
portion of the assignment below.
Testing
You should test different sequences of local (and for extra credit, remote) commands
on a set of inter-connected clients and servers involved in multiple
games of different types. Testing on multiple machines is strongly encouraged
to catch problems that may not show up in a single machine setting.
First make sure that the basic features function correctly, and then
define and execute more sophisticated scenarios to test the different
sequences of potentially interacting configuration commands that cannot
be tested thoroughly simply by exercising each command individually.
Writeup
In addition to documenting your design decisions and solution approach
for this assignment, your README file for this lab should summarize
the design challenges, patterns, and decisions you made throughout the
semester, according to the criteria listed in the "what to submit"
section of this assignment, below.
Resources
There are a number of resources available that can greatly ease your
task in completing this assignment. This project is designed to be
straightforward if you study (and use) the techniques mentioned
in this section, and will be much more difficult if you do not.
Wrapper Facades
The following Wrapper Facades are provided by the ACE version
installed on the CEC Linux machines at
/home/cec/class/cse532/ACE_wrappers/ace/ and are highly useful for this assignment:
- ACE_Service_Object -- declares interfaces for initialization, termination, suspension, and resumption of
reactor event handlers
- ACE_Service_Repository -- stores and provides access to service objects
- ACE_Acceptor -- a generic abstraction for accepting socket connection requests
- ACE_Connector -- a generic abstraction for requesting network connections
- ACE_Get_Opt -- an option-string parameterized abstraction for
parsing command line arguments
- ACE_INET_Addr -- an internet domain socket abstraction
- ACE_Reactor -- a generic abstraction for reactive event demultiplexing
- ACE_SOCK_Acceptor -- an acceptor abstraction for sockets
- ACE_SOCK_Connector -- a connector abstraction for sockets
- ACE_SOCK_Stream -- an internet domain stream socket abstraction
- ACE_Svc_Handler -- a full featured service handler abstraction
- ACE_Thread_Manager -- a class used for managing threads directly
- ACE_Thread_Manager -- a class used for managing threads directly
- ACE_TSS -- an implementation of thread specific storage
- ACE_Guard -- an implementation of a guard for scoped locking
- ACE_Thread_Manager -- a class used for managing threads directly
- ACE_Null_Mutex -- implements a do-nothing mutex (for strategized locking)
- ACE_Thread_Mutex -- implements a thread mutex synchronization object
- ACE_Recursive_Thread_Mutex -- wraps or emulates a recursive thread mutex
- ACE_RW_Thread_Mutex -- implements a readers/writer thread mutex
- ACE_Null_Semaphore -- implements a do-nothing semaphore (for strategized locking)
- ACE_Thread_Semaphore -- implements a thread semaphore synchronization object
- ACE_Condition_Thread_Mutex -- implements a thread mutex condition variable
- ACE_Message_Queue -- implements a queuing layer used with multi-threading
- ACE_Task -- active object that can interoperate with other threads, reactors, etc.
Service dimensions and the Service Configurator framework in ACE are
discussed in detail in chapters 2 and 5 of the C++NPv2 book:
- Schmidt and Huston C++ Network Programming, Volume 2, Addison-Wesley, 2003.
The Reactor and Acceptor/Connector pattern implementations in ACE are
discussed in detail in chapters 3, 4, and 7 of the C++NPv2 book.
The concurrency and synchronization pattern implementations and
wrapper facades in ACE are discussed in detail in chapters 5, 6, 9,
and 10 of the C++NPv1 book:
- Schmidt and Huston C++
Network Programming, Volume 1, Addison-Wesley, 2003.
Implementation of the Active Object and Half-Sync/Half-Async patterns
and their associated wrapper facades is discussed in detail in chapter
6 of the C++NPv2 book.
STL Iterators and Algorithms
Using the STL copy (or possibly transform)
algorithms to move data between files, sockets, containers, and/or
output streams is a basic requirement of this assignment. Studying
and using the following STL abstractions can make your task of
completing this assignment much easier:
- input iterators -- Austern 7.2, pp. 94-96
- output iterators -- Austern 7.3, pp. 96-100
istream_iterator -- Austern 14.2.1, pp., 354-357
ostream_iterator -- Austern 14.2.2, pp., 357-359
for_each algorithm - Austern 11.4, pp. 218-220
copy algorithm - Austern 12.1.1, pp. 233-235
transform algorithm -- Austern 12.3, pp. 240-243
- unary function objects -- Austern 8.1.2, pp. 111-112
- sequence containers -- Austern 5.3.1, pp. 73-75
list container class -- Austern 16.1.2, pp. 441-448
Makefile & Environment
The GNU C++ compiler (g++) is installed on the
CEC Linux machines. You will need to set a few environment
variables (it may be convenient to keep these in a small script
file and/or simply set them in your .cshrc or .bashrc file):
setenv ACE_ROOT /home/cec/class/cse532/ACE_wrappers
setenv LD_LIBRARY_PATH ${ACE_ROOT}/ace:${LD_LIBRARY_PATH}
You will need a Makefile that links appropriately with the ACE
libraries and include files. Please feel free to adapt or use the
Makefile from your lab 2 solution.
What to Submit
README
When you have completed your program, please document your solution in
a text file called README.
The first section of your README file should include:
- the number of the lab (e.g., "CSE 532 Spring 2008 Lab 3")
- the names and e-mail addresses of all team members
- an overview of how your programs are designed,
- the Wrapper Facades you used or extended and how they helped in
your design and code,
- any insights and questions you encountered while completing the
assignment, and
- if you are applying for extra credit please also describe the
additional configuration capabilities your approach supports, explain
your design and implementation choices for them, and give instructions
for how to use and test them.
The second section of your README file should provide a detailed
discussion of how your lab solutions evolved during the semester,
from lab 0 through lab 3. For each of the lab assignments, please
discuss
- what the main design challenges were,
- which of the design patterns we discussed in the course were
applied in your lab solution,
- how those design patterns helped address the design challenges you faced,
- why you chose those design patterns instead of other alternatives (if any), and
- how your implementation reflected the design patterns you chose (for example,
which classes in your implementation played which roles in which patterns).
The third section of your README file should provide detailed
instructions for how to:
- unpack
your files,
- build your programs, and
- run your programs.
Please
indicate any and all environment variable settings, etc. you are
assuming will be needed for this. I should be able to receive your
e-mail, and unpack, build, and run your programs using only the
instructions in your README file and the tools available on the CEC
Linux machines.
Electronic Submission
Please submit by e-mail to cse532@cec.wustl.edu a single
file containing: - your source code files,
- your Makefile,
- any example files you think useful, and
- your README file.
You
can use the uuencode, tar, and
zip or gzip tools to do this, as in the
Makefile provided for lab 2.
Please send a separate copy of your README file, with
clear instructions on how your submission file was produced and how to
unpack it using tools available on the CEC Linux machines.
Grading
Please treat this assignment as you would a commercial or research
product that you are shipping to a large group of customers. Please
take the time to test, document, and check your work to make sure all
parts are shipped, and are of high quality. Grading will focus both
on the quality of your solution and on the quality of the product
itself, as it arrives at the cse532 account.
To ensure the best possible result (and accordingly the highest
possible grade), please pay attention to the following issues:
Correct Compilation and Operation
Your program must compile and run correctly on the CEC Linux machines
using the Makefiles and source files you provide. Please make sure
your program compiles without warnings, and you might even want to try
different compilers and fix all the warnings for each. Problems with
Makefiles will be handled similarly to missing files, with a 5 point
deduction if you need to supply a new Makefile in order for me to
build your solution on the CEC Linux machines.
Design Quality
Design decisions are largely yours to make, and as long as the design
is concise, coherent, consistent, and addresses all design forces in the
assignment, you will receive full credit. One key area to consider
is whether each abstraction in your design does a single job, does
that job completely, and collaborates appropriately with other
abstractions. Minor deductions (1-3 points, but please be aware minor
deductions can add up) will be made for abstractions
that are unnecessarily large or have inappropriate inter-dependencies.
Major deductions (5-15 points) will be made for larger problems like
neglecting key design forces in the assignment.
Implementation Quality
How you implement your solution is again up to you, and I will take
into account different approaches to implementation. Minor deductions
will be made for things your program gets away with but are not good,
like neglecting to check the return value from a system call or
Wrapper Facade method (i.e., the program may have problems under
special conditions) or calling exit(1) from inside a class method
rather than providing a clean termination path, while major deductions
will only be made for problems that produce incorrect or extremely
inefficient behavior of the program. The former kinds of errors
should be eliminated during your coding and code review phases, and
the latter kinds of errors should be eliminated during your testing
phase.
Coding Style
Different coding styles will also be accepted, as long as they are
clear, readable, and consistent. Please code clearly with both the
reader of the code and the user of the program in mind. Please use
consistent indentation and placement of braces, and comment your code
thoroughly. Use whitespace liberally - it's free and it makes it a
lot easier to read your code. When grading, I will add tagged
comments to the code indicating areas where I found particular issues
to mention, whether or not points are deducted. Only minor deductions
will be made for each style issue, except in extreme cases.
Documentation
Please make sure you provide adequate instructions on how to unpack,
build, and run your program. Also, please make sure to document your
solution. Minor or even major deductions will be made for inadequate
explanation of how your solution does what it does, how you made key
design choices, or how the user (or grader) can successfully build and
run your program. Even if how you did something is obvious to you,
please assume it is not obvious to the reader.
Missing Files
Missing files in the delivered software make it difficult or
impossible to evaluate your solution. An automatic 5 point deduction
will be applied for each missing file that is submitted later on request.
Late Submission
Labs submitted within 24 hours of the deadline will be graded with an
automatic 10 point deduction. Labs submitted more than 24 hours after
but within 48 hours of the deadline will be graded with an automatic
20 point deduction. Labs submitted after 48 hours from the deadline
will not be graded except in extreme cases of extenuating
circumstances. If you are running late completing the assignment,
please let me know about the trouble as soon as possible, and please
turn in as much as you can before each deadline so I can give at
least some credit for the work you have done.
Grading Issues from Previous Labs
Please avoid the following practices, for which I have made comments
and possibly deductions in the current semester and previous
semesters, depending on the extent of the issue:
- using hard coded constants
- using dynamic allocation when a class member would do: if it's dynamically allocated in the constructor and dynamically
deallocated in the destructor, then there must be some reasonable design force that motivates this approach rather than
using an object directly as a member of the class (which your readme file should of course please explain)
- using the same error message for different failures
- using a pointer/size arithmetic formula without a comment explaining it: in general any non-obvious code should have
a comment giving an intuition of what it does in general terms (and even better explaining why or how it does it that way)
- putting non-inline function definitions in a header file
- initializing class members in the constructor body when it is possible to intialize them in the base/member initialization list
- initializing class members in the base member initialization list when that initialization could fail, e.g., when
a function whose return value matters or that could throw an exception is called
- unnecessary format restrictions, e.g., not tolerating blank lines and extra whitespace in a file
- leaving "commented out" code fragments left in code - remove these or use conditional compilation to bring them in/out
- adding comments for things that are already fairly obvious from the code (e.g., begin and end of a method)
- using exit to end the program in the middle of a class method or any method that could be called from another method
- using fixed length buffers when the lengths of inputs to them are variable/unknown
- unnecessarily cramping symbols or lines together, which impacts readability - use whitespace generously: it's free!
Please use the following practices to improve the clarity and quality of your code and documentation:
- use precompilation include guards in header files to avoid duplicate inclusion:
#if ! defined (MY_FILE_H)
#define MY_FILE_H
// body of the header file
#endif /* defined MY_FILE_H */
- check the return value from any call that could return an error code after a failure (e.g.,
new (nothrow) )
- use try/catch blocks around any call that could throw an exception after a failure (e.g.,
new)
- use the constructor's base/member initializer list in preference
to the body of the constructor for member intialization, except
were it is necessary to use the constructor body (please see above)
- using typed constant variables with descriptive names in preference to precompiler
#define macros
- use consistent and appropriate comments
- use inlining for efficiency, and use the technique in ACE that allows inlining to be turned on or off by
a precompilation macro
- discuss design decisions in your README file
- ask remaining questions you have in your readme (!)
- test your code for its handling of error cases as well as of the "happy path"
- perform "tiger team" testing - take turns trying to find cases that break your program, and then fixing them
- using the string class for variable length character buffers
- using whitespace generously: it's free and it improves readability!