CSE 532 Spring 2008
Lab 1: "Reactive Card Game"
Points: 10% of final grade
Teams: declared or assigned by 11:59pm Sunday February 16, 2008
Submitted: by electronic submission to cse532@cec.wustl.edu
Due: by 11:59pm Sunday March 2, 2008 (Note the New Deadline)
Purpose
The purpose of this assignment is to introduce the basic ideas of
reactive concurrency, of active and passive connection
establishment, and of 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 lab. You
should also use algorithms, containers, iterators, and functors from the
STL to simplify the programming of this assignment. In this lab we will focus
on integrating the Reactor with the Acceptor and Connector in ACE, to
allow cards to be dealt to each client by a server, and for clients to
score their hands and send the scores to the server who will declare a winner
(or a tie). We will use these ideas and the
design and implementation skills you'll develop using them, repeatedly
throughout the rest of the semester.
Assignment
In this assignment you will modify your Lab 0 server and client
programs so that each client can be connected to multiple servers, as
well as maintaining the feature from the previous lab that each server
can be connected to multiple clients. All clients and servers will
play the same kind of card game (5 card stud poker) but each server will
run its own game instance (or instances, differentiated by game names)
The connection semantics of your client and server programs will be
similar to those in Lab 0. Servers should again be started first and
should listen passively for connections from the clients. Each client
should initiate a separate connection to each server that is listed in
a client definition file that is specified when the client is started, and keep
each connection alive until it is closed down by the server on the other
end or until the client itself is shut down. A new key
feature of both servers and clients for this lab, is that they should
be able to manage multiple connections at once reactively.
To do this, please change both the servers and clients from your Lab 0
implementation to use the reactor and acceptor/connector patterns so
that each can react to events on multiple ports at once. Each server
should be able to respond to events on its established sockets and the
port on which it's listening for new socket connections, for arbitrary
patterns of arrival of those events. Similarly, a client should not
wait for each server to respond to its connection initiation requests,
but rather should send connection requests to all of the servers
specified in its client definition file as quickly as possible, and
wait reactively for connection completion events to occur, and
register and operate service handlers for established connections even
while it waits for any remaining connection establishment requests to
be completed.
These requirements mean that both the acceptor on the server side, and
the (possibly multiple) connector(s) on the client side, should be
registered as handlers in a local reactor. The acceptor and
connector(s) should create separate service handlers for each
connection they establish, and should also register each such handler
with the local reactor. The overall picture is that all
concurrency is managed within a single thread in each server or client
process, using the reactor to multiplex events from a number of
sources onto that thread.
To do this please perform the following tasks:
Servers
- Modify your server program from Lab 0 so that the different concerns of
- Accepting connections and creating and registering new handlers for the new connections,
- maintaining the data structure that associates players with games, and
- reading and writing message traffic on each connection
can all be achieved in a reactive environment.
To consider the most obvious approach first, it is possible simply to extend your
Dealer class so that it implements the necessary handler
and acceptor interfaces so that it can play all three roles. However,
it is likely that a better separation of concerns, and thus a simpler
and probably more effective design, can be realized by using separate
classes, e.g., Dealer_Acceptor and
Dealer_Svc_Handler classes (possibly derived from
appropriate base classes obtained from ACE), for the first and third concerns, and
updating your Dealer class from Lab 0 as needed so that it can
address the second concern while working with the other two classes.
However you decide to approach this design question, please document
your design decisions on this topic in your README file, explaining
the design forces you considered in making each design decision, and
discussing how your mapping of these concerns into the classes you
decided to use best addresses those design forces.
- The same kind of simple main function as in Lab 0 should be used
to initialize each server from the command line, using switches
with the following syntax:
server -n <dealer name> [-p <server port>]
where:
<dealer name> is a string with no intervening spaces
(for example, -n Dealer1),
and <server port> specifies the port number at which the server
should listen for connections.
The -p switch should be optional, and if one is not
specified the server should default to listening on some port
number specified by the Dealer class: please remember ports
through 1024 are reserved for protocols like telnet, ftp, or
http, so you should only use port numbers greater than 1024.
Also, you may want to try a different port (and/or run ps
-u <uid> to see what processes you already have
running) if the server fails to bind on a given port.
If the user fails to specify either the -n switch, or gives
another switch not recognized by your sensor program (you are free to
add any additional switches you think useful), your program
should print out a useful syntax message before exiting, like
the following:
usage: server -n <dealer name> [-p <server port>]
- When the server is first initialized (after the command line
parameters have been processed) it should print out its dealer's name to the
standard output stream.
- The server program should create or otherwise obtain a reactor
that it can use to demultiplex connection requests
onto the acceptor, and to demultiplex service requests onto
service handlers which the acceptor creates
and registers with the reactor, according to the reactive version of the
Acceptor/Connector pattern. A very straightforward way to do
this for this assignment, is to use the
ACE_Reactor::instance() singleton (see the GoF book for a detailed
discussion of the Singleton design pattern) as described on page 79
of the C++NPv2 book. In fact, several of the key methods of the
classes that will work with the Reactor, such as the ACE_Acceptor's
open method, have parameters that are defaulted to use this
reactor instance unless another reactor is supplied. Please read
Section 3.5 of the C++NPv2 book carefully, as it gives lots of good
details about the ACE_Reactor wrapper class and its use.
- The server program's acceptor should again listen on the given port
(or a default port if none is given) on the host machine where it
is running, but in this assignment it should do so reactively, as
described in Section 7.3 of the C++NPv2 book. To bootstrap this, the
server program should first invoke the acceptor's open call with the
appropriate reactor instance. When that call succeeds, the
program should then again obtain the server's listening address
and port number as a string from the address on which it was
asked to listen, and print them out to the standard output stream
or other location where you can give it to the client. After the
acceptor is registered with the reactor, the server program's thread
of execution should then enter the reactor's event loop, by calling
the reactor's
handle_events method.
- Whenever the server program's acceptor's handle_input() method
is invoked by the reactor, it should:
- accept a socket connection request from a client,
- create a new instance of a socket service handler for that
connection and associate the connection with the handler,
- ensure the service handler is registered with the reactor for both
reading and writing, and
- allow the thread of execution to return to the reactor's event loop.
- Whenever a server service handler is opened, it should
reactively perform the following activities in
sequence:
- send its dealer name to the client on the newly created
connection;
- read the player's name from the newly created connection;
Note that you are again free either to have the server send
its name first and then wait for the client to send its name (which
seems simplest), or if you prefer you can have the client send its
name first, and then have the server reply with its name (either is
fine).
- perform repeated reads to obtain the complete series of game names: as in the previous
lab, how you structure the protocol that allows the server to know when it has read
all of the game names that the client has sent is up to you.
Reminder: again, be careful with this part of the program, as the server can
become deadlocked if its (only) thread is trying to read content
from the socket that will never arrive - only a single read call (not a read_n or recv_n call) on the socket can
be done safely within one call from the reactor, so the handler may need to read and
remember incomplete segments of data temporarily, until additional reads are
done during additional calls from the reactor.
- once the handler has obtained sufficient information from the socket, it should
read through the list of games, add any new games that it didn't have listed previously,
and associate the client's name and socket with each of the games in the
server-side data structure(s);
- as soon as there are at least 5 players in a game, the server should create and shuffle a new
deck of cards (using STL containers, iterators, functors, and algorithms as much as possible to
implement this), and send 5 cards to each of the players in the game - if new players join the game
after the first then cards should be dealt to them as well, as long as the game is still in play.
The shuffling should be pseudorandom even with respect to different runs of the server, so that the
same shuffle won't occur each time the server is run. One way to achieve this is to call
srand(time(0)) and then use the rand function to obtain a randomized number
of times to call the STL's random_shuffle function. Also, how you implement cards is up
to you - an STL pair composed of enumerations for suit and rank (with appropriate additional methods
for converting to and from a string that represents a particular suit/rank combination) is one reasonable
way to do this, which allows cool iterator abstractions to be built for initializing a deck of cards, etc.
- the server should reactively wait for the clients to send back their scores for their hands, and when
all of the players in the game have sent their scores then the dealer should rank the scores, print out
the scores in descending order with each player's name next to their score, and declare the game to be over.
Note: again, be careful with this part of the program, and only do one read in each call from
the reactor.
Note: The reactor distinguishes a socket becoming
ready for reading or for writing as separate events. A service
handler for a socket can be registered as either a read or write
handler, or both. While it is reasonable (and probably beneficial)
for this lab that one service handler object could manage both the
reading and writing activities, it is useful to consider how to design
the handler's handle_input and handle_output
methods to deal with cases where one is invoked when the handler is
not ready for that phase (say when an input ready event on the
incoming socket for a stream occurs during the time the server is in
the middle of sending a previously read message to another client in
the game). For example, the handler could either (1) continue with
its current phase of operation and ignore the spurious event until it
is appropriate to handle it, or (2) record information from one call
(say saving the bytes read from the incoming stream) before returning
to the activity that was interrupted (writing to the outbound stream).
In designing your handlers on the server, please also try to maximize
the concurrency of the handlers that are reading the incoming data
and those that are writing data to the other players in the game, but also
recognize that there is likely to be a point of diminishing returns
past which additional concurrency may cost too much in terms of
overhead or in implementation complexity.
- If the client drops its end of the connection, the appropriate service handler should
be removed from the reactor, release its resources gracefully, and be destroyed. For this
assignment as in the previous one it is ok simply to shut down clients and servers using
Ctrl-C from the keyboard, though in the next lab we will add a more graceful
shutdown mechanism and protocol.
- To demonstrate your server, you should please start at least two
separate copies of the server program (using a different port and/or
host machine for each), as in:
server -n Dealer1 -p 10070 &
server -n Dealer2 -p 10071 &
Clients
- In this lab you will make several modifications to your Lab 0 client implementation.
- Similar to the server program, each client should create or
obtain a reactor as discussed above.
- Modify the main function to initialize the client from the
command line, to use switches with the following syntax:
client -n <client name> -f <definition file>
where:
<client name> is a string with no intervening spaces
(for example, -n Player1), and
<definition file> is a string with no intervening
spaces that gives the name (and possibly also the directory path if needed)
for the client's definition file: (for example, -f
Player1Definition).
If the user fails to specify either the -n or -f switches, or gives
another switch not recognized by your program (you are free to
add any additional switches you think useful), your client program
should print out a useful syntax message before ending gracefully, like
the following:
usage: client -n <client name> -f <definition file>
- Using the file name given with the -f switch, your client program should
open that file and parse its contents in preparation to making connection
requests and communicating with servers once connections are established.
If the file given by the -f switch cannot be opened or read, the client program should
print out an error message indicating the kind of problem it encountered,
then release its resources and shut down gracefully.
The format of the client's definition file is up to you to choose,
though the following text file example in Player1Definition
is a reasonably good (and simple) approach. In general each line of the file should specify:
- a game name,
- optionally, the port (using the same default port value as the server's default,
if a port is not specified) for the server on which the client will join the game, and
- optionally, the host name or IP address (using the same host as the client as
a default if one is not specified) for the server on which the client will join the game.
Note that the use of -g, -p, and -i switches in the file may help you to re-use
most of your command line parsing implementation from Lab 0. In particular, if you
first tokenize each line into an argv-like array of character string pointers, then you can
use an instance of ACE_Get_Opt directly to parse each line's switches. Note also that
using switches allows a switch to be used more than once on a line, such as to specify
more than one game that a client wishes to join on a server (this is an optional feature
for now, but it'll be increasingly useful in later labs). If a line in the definition file does not give a game name
your client program will not be able to use that line, and should print out an error message for that
line, but should then continue to parse later lines and try to set up and use
those connections.
Again, please remember not to use reserved ports
or to use the same ports as another process running on the same
machine. In fact, you should probably not use the port numbers
in the examples given in this assignment, assuming someone else might use them too and be
running programs on the same machine. Also, if your program cannot bind
on a port, you might want to (after verifying your code is
correct) try another port number, and/or run ps -u
<uid> to see what processes you already have
running) if your program fails to bind on a given port.
- For each well-formed line in the definition file, the client should:
- initialize a connector to request a connection to that server, and register the connector with the reactor
- initiate a socket connection to the server at the address for that
server, but allow the completion of that request to be handled reactively and continue to the next
server in the list rather than blocking on the connection establishment request.
When the client program has finished creating and registering
connectors and initiating connection requests on them, it should call
the reactor's handle_events method to start the event loop.
- When each connection request completes, the connector should create a service handler for that connection
and register that service handler with the reactor.
- Each service handler should perform the following steps reactively (again using only one read or write per
call from the reactor) and in sequence:
- send the player name to the server over the newly created connection;
- read the server's name from the connection and
print out a message to the standard output stream indicating the name of the server
to which it has connected;
- send the server a list of
the games it wants to join on that server: again, how you structure the protocol for sending
the list (in particular, how to detect the end of the list) is up to you, but watch out
for cases where the server and client could have different expectations which can lead
to deadlock;
- After the client-side service handler has sent its list of
game names, it should reactively read from the socket until it has received 5 cards. Once all 5 cards
have been received, the client-side service handler should score the hand by applying a functor (described next)
to the cards and generating a score for the hand. The
functor should score the hand by producing a value that encodes the same partial order as the ranking
of hands in standard 5-card stud poker. Kevin Kunkee suggested one nice way to do this in the Fall 2007
edition of CSE 332: construct a 6-digit base-13 integer value that uniquely identifies each hand (note
that there are many polynomials that could be used to uniquely index the possible hands, which you are
free to use if you prefer, as long as the one that you pick distinguishes the hands in the right order).
the highest order digit encodes the rank of the hand, according to the standard poker
ranking of hands and cards, where an ace can be used as the lowest card in a 5-4-3-2-A
straight or straight flush, but otherwise is the highest card. The value of the highest
order digit should be selected as one of the following:
8: straight flush - all cards have the same suit and are consecutive in sequence.
Note that an ace can be high (A-K-Q-J-10) or low (5-4-3-2-A) but cannot be used
to bridge between the two and king.
7: four of a kind - four of the cards have the same rank.
6: full house - three of the cards have the same rank, and the other two cards have the same rank.
5: flush - all cards have the same suit but are not in consecutive in sequence.
4: straight - all cards are consecutive in sequence but are not all of the same suit.
Note that as with a straight flush, an ace can be high (A-K-Q-J-10) or low (5-4-3-2-A)
but cannot be used to bridge between the two and king.
3: three of a kind - three of the cards have the same rank, and the other two cards are of
distinct other ranks.
2: two pair - two of the cards have the same rank, and two of the other cards are both of
the same rank (but different than the rank of the other pair).
1: one pair - two of the cards have the same rank, and the other three cards are of
distinct other ranks.
0: no rank - none of the other hand ranks is applicable.
To construct the integer, first multiply the high order digit by 13 to the 5th power, and then
fill in the remaining digits of the base-13 number as follows according to the hand's rank:
straight flush or straight: take the product of the rank of the highest card
(A is 12, K is 11, ..., 2 is 0) times 13 to the 4th power, and add it to the integer.
four of a kind: (1) take the product of the rank of the four cards
times 13 to the 4th power and add it to the integer, then (2) take the product of the rank of the
remaining card times 13 to the 3rd power and add it to the integer.
full house: (1) take the product of the rank of the three cards
times 13 to the 4th power and add it to the integer, then (2) take the product of the rank of the
two cards times 13 to the 3rd power and add it to the integer.
flush or no rank: (1) take the product of the rank of the highest card times 13 to the 4th power
and add it to the integer, then (2) take the product of the rank of the next highest card times
13 to the 3rd power and add it to the integer, then (3) take the product of the rank of the next
highest card times 13 to the 2nd power and add it to the integer, then (4) take the product of the
rank of the next highest card times 13 and add it to the integer, and finally (5) add the rank
of the lowest card to the integer.
three of a kind: (1) take the product of the rank of the three cards times 13 to the 4th power
and add it to the integer, then (2) take the product of the rank of the higher of the two remaining
cards times 13 to the 3rd power and add it to the integer, then (3) take the product of the rank of
the lower of the two remaining cards times 13 to the 2rd power and add it to the integer.
two pair: (1) take the product of the rank of higher pair of cards times 13 to the 4th power
and add it to the integer, then (2) take the product of the rank of the lower pair times 13
to the 3rd power and add it to the integer, then (3) take the product of the rank of the
remaining card times 13 to the 2rd power and add it to the integer.
one pair: (1) take the product of the rank of the pair of cards times 13 to the 4th power
and add it to the integer, then (2) take the product of the highest remaining card times 13
to the 3rd power and add it to the integer, then (3) take the product of the rank of the
next highest remaining card times 13 to the 2rd power and add it to the integer, then (4)
take the product of the rank of the lowest remaining card times 13 and add it to the integer.
- The client should then print out the name of the game, the cards in the hand, and the score, all on one line.
Once a client has done that, it should reactively send the score for that hand to the server.
- If the server shuts down the connection, the service handler should release its resources gracefully
(e.g., by calling close on
the socket), be removed from the reactor, and be destroyed. As with the server, it is ok to shut clients
down simply by using Ctrl-C from the keyboard to end their process, though for the next lab we
will implement a more robust and graceful shutdown mechanism and protocol.
- To demonstrate your client class, you should first please start
separate copies of the server program (using a different port and/or
host machine for each), and then start separate copies of the client, as in:
client -n Player1 -f Player1Definition
client -n Player2 -f Player2Definition
client -n Player3 -f Player3Definition
client -n Player4 -f Player4Definition
client -n Player5 -f Player5Definition
Resources
There are a number of resources available that can greatly ease your
task in completing this assignment. This
project is designed to be very straightforward if you study (and use)
the Wrapper Facades mentioned in this section, and will be much more
difficult if you do not.
Also, there are several more mundane (but as in any software
project, important) areas where you can exploit tools and examples to
greatly speed your progress on this project and the ones later in the
semester. In particular, code fragments and Makefile examples are
provided as exemplars to follow or discard, as you choose.
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_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
The Reactor and Acceptor/Connector pattern implementations in ACE are
discussed in detail in chapters 3, 4, and 7 of the C++NPv2 book:
- Schmidt and Huston C++ Network Programming, Volume 2, Addison-Wesley, 2003.
EXTRA CREDIT:While you should at a minimum document
your design decisions for particular issues requested in the
assignment itself, labs that do a particularly thorough job of noting
the important design issues, the alternatives that were considered but
not done, the factors that affected each decision that was made, and
how each such decision worked out, will be awarded additional points
(up to 5 out of a total value for the assignment of 100).
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
following example Makefile.
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 1")
- 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, and
- any insights and questions
you encountered while completing the assignment (including the design decisions
discussion for which extra credit will be considered).
The second 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 (for example, a copy of the
NW_servers.txt file showing the format you chose),
and
- your README file.
You can use the uuencode,
tar, and zip or gzip tools to
do this, as in the provided example Makefile.
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 set 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
Please avoid the following practices, for which I have made comments
and possibly deductions in 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, i.e., 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!