Style and Elegance
When you write code, readability should be your top priority!
To the extent that you can do it without sacrificing readability, you should maximize maintainability and extensibility.
And remember: Nobody writes beautiful code all the time!
So it's important to go back and fix things.
Consistency
Use (In)Consistency to Your Advantage
-
Similar things should look similar.
-
Different things should look different.
But (In)Consistent With What?
- With ourselves (within a file, and across files in a single program).
- With the culture of the company, organization, or team that we're developing software with.
In CS 70, that means that your code should be internally consistent, and that it should be consistent with the CS 70 course style guide. Generally speaking, we follow Google's Style Guide, and you'll be able to use a tool called cpplint
that checks Google style on homework assignments to catch some style issues.
Can you tell us a little more about the style expectations in CS 70?
I'm so glad you asked!
Consistency: Variable Names
All variable names and function names should be written in “camelCase”. Start the first word with a lower-case letter, and subsequent words with a capital letter, with no underlines between them. Valid names include count
, i
, activeTask
, and buildBarn()
When we learn about classes in C++, a special type of variable called a data member will also be named in camelCase, but with a trailing underscore. Possible data member names include front_
and currentCapacity_
.
Class names will always have a capitalized first letter, and the rest of the name will be in camel case. We could name a class Gene
or StudentTranscript
.
Constant values will be named in all caps, with an underscore between words. We might see constants named VERSION
or MAX_STUDENTS
.
So that means that we can tell just by looking at the name whether something is a regular variable, a data member, a class, or a constant!
Yes! Similar things look similar, and different things look different!
Consistency: Applying Idioms
The primary audience of the code we write is other humans. We want other coders to be able to focus on the interesting parts of our code that solve the problems we're interested in.
That means that as much as possible, we should follow the accepted conventions of the coding community we're part of. An experienced C++ programmer shouldn't have to spend mental energy understanding the structure of our for loop, so long as we use the patterns that are expected by people who look at a lot of C++ code.
In C++, for loop index variables should start at 0 and go up to (but not including) the iteration count. variables should almost always be incremented using the syntax ++i
instead of the syntax i++
constexpr size_t NUM_LETTERS = 26;
string alphabet;
for (size_t i = 0; i < NUM_LETTERS; ++i) {
alphabet += ('a' + i);
}
Incrementing this way is called pre-incrementing.
But wait… I learned some C once, and they used
i++
.That's right! In C, post-incrementing is more idiomatic for reasons lost in the ancient history of the language. In a for loop like this one, they do almost exactly the same thing (add one to the value of
i
). Let's look at the differnce. If we had this code and ran itint i = 41; std::cout << i++;
it would print
41
even though afterwards
i
would have the value42
. Becausei++
means “stash the old value ofi
somewhere, incrementi
, and then give me the old value that you stashed away”.In contrast, if we'd said:
int i = 41; std::cout << ++i;
it would print
42
So
++i
just means “incrementi
and give me its value”.The first one is weird, the second one actually makes sense.
Exactly! We could live happy productive lives and never want that weird old-value stashing behavior.
So why do people use
i++
I wish I knew! It's like a cargo cult where everyone copies what everyone else does. When you throw away the value rather than printing it and the thing you're incrementing is an
int
, it doesn't do any harm to usei++
, but it's still a bit pretty odd to ask a language to go to all the trouble of remembering the old value just to throw it away, unused!So if they're basically the same for most places we will use them in CS 70, why does it matter which one we use?
Because other C++ programmers expect to see pre-incrementing. If you post-increment a variable, they'll notice. And then they'll spend time wondering why you didn't pre-increment, when they could be appreciating the beauty of the rest of your code!
Ooh, tell them about the C++ name!
The name “C++” is kind of a joke. It's like an increment beyond C, but you still get all the old value that C had.
Consistency: Whitespace
Unlike Python, C++ is whitespace insensitive—it doesn't care how far you indent your lines, or whether you put your curly braces on the end of one line or the start of the next line. But using consistent whitespace and bracketing placement can make it much easier for others to understand the structure of your code, and can lead to fewer logic errors. Some highlights of the style guide we follow in CS 70 include:
- No lines longer than 80 characters: Longer code lines can wrap in weird ways, making it harder to follow your code.
- Inside blocks (functions, conditionals, loops, etc.) use a consistent amount of space. (VS Code's default is good!)
- Opening curly braces go on the end of the line before the block they enclose. For functions, curly braces go with the return type, name, and parameters of the function. For loops and if statements, curly braces go with the
for
orif
. - Closing curly braces go on their own line, indented to match the start of the block they close.
More details can be found on Google's C++ Style Guide. Don't worry about the parts that you don't understand yet; we'll learn (more) features of the language as the semester goes on!
Exceptions
- Google's style guide recommends a two-space indent, perhaps because a lot of code at Google is deeply nested. In CS 70, we don't encourage deeply nested code so we use a four-space indent (our style checker,
cpplint
allows both). - Google's style guide allows very short functions to be written all on a single line (in violation of the rule above). We don't allow that.
Comprehension Check
The CS 70 Hall of Shame
We've scoured the Internet (so you don't have to!)
We found the worst of the worst.
The ugliest of the ugly.
The grossest of the gross.
Presenting… Your candidates for the CS 70 Coding Style Hall of Shame!
Your mission, should you choose to accept it, is to review the candidates below. Then pick one to nominate based on its terrible coding style.
Candidate A: A Capital Idea
std::string capitalizedName(std::string name)
{
if (name == "aafke") {
return "Aafke";
} else if (name == "aaron") {
return "Aaron";
}
// lots and lots of similar lines, not shown
} else if (name == "zuzana") {
return "Zuzana";
} else if (name == "zuzanna") {
return "Zuzanna";
} else if (name == "zuzanny") {
return "Zuzanny";
} else {
// Name not in the database yet,
// but lowercase is better than nothing
return name;
}
}
Candidate B: Seems Valid to Me
bool validateSSN(std::string ssn) {
if ( ssn[0] != '0' && ssn[0] != '1' && ssn[0] != '2' && ssn[0] != '3' && ssn[0] != '4' && ssn[0] != '5' && ssn[0] != '6' && ssn[0] != '7' && ssn[0] != '8' && ssn[0] != '9' ) {
return false;
}
if ( ssn[1] != '0' && ssn[1] != '1' && ssn[1] != '2' && ssn[1] != '3' && ssn[1] != '4' && ssn[1] != '5' && ssn[1] != '6' && ssn[1] != '7' && ssn[1] != '8' && ssn[1] != '9' ) {
return false;
}
// plus 7 more similar cases ...
return true;
}
Candidate C: Twenty(-ish) Spaces
// Yes, this is the whole example. :)
const std::string twentySpaces = " ";
Candidate D: Spudfisser???
void SPdfsR(Graph G, int s)
{ link u; int i, t; double wt;
int **p = G->path; double **d = G->dist;
for (u = G->adj[s]; u != NULL; u = u->next)
{
t = u->v; wt = u->wt;
if (d[s][t] > wt)
{ d[s][t] = wt; p[s][t] = t; }
if (d[t][t] == maxWT) SPdfsR(G, t);
for (i = 0; i < G->V; i++)
if (d[t][i] < maxWT)
if (d[s][i] > wt+d[t][i])
{ d[s][i] = wt+d[t][i]; p[s][i] = t; }
}
}
Candidate E: surelyWonderfulJavaFunctionWhichHasBeenLabeledAsSuch
// This is Java code to critique, not C++ syntax!
synchronized (surelyReachableObjectsWhichHaveToBeMarkedAsSuch) {
waitRecommended =
surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContentedSize
== surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContented.size();
surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContentedSize =
surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContented.size();
while (!surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContented.isEmpty())
{
surelyReachableObjectsWhichHaveToBeMarkedAsSuch.push(
surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContented.getFirst() );
}
}
Candidate F: is03or09or10orAwesome
/**
* function is03or09or10.
* takes: prodCode
* returns: bool
*/
bool is03or09or10(std::string prodCode)
{
if ("03" == prodCode) return true;
else if ("09" == prodCode) return true;
else if ("10" == prodCode) return true;
else return false;
}
// other functions not shown: is01, is02, and is004or005
Candidate G: I Feel Validated
void validate(Person p)
{
const int TWO = 2;
bool validated = true;
std::string myMessage, val;
// Name
val = p.name;
if (!(val.find(" ")!=std::string::npos)) || !(val.size()>TWO)) {
myMessage += "Please fill in your full Name\n";
validated = false;
}
// Address
val = p.address;
if (!(val.size()>TWO)) {
myMessage += "Please fill in your full Address\n";
Candidate H: Alphabet Soup
bool b(std::string a) {
int b = -1, c = a.length();
goto p;
while(b<c){
if (a [b] != a [c]) return false;
p: ++b; c--; }
return true;
}
Candidate I: Go With the (Control) Flow
// In this example, we've omitted everything except the control flow,
// so that you can get a sense of how that aspect of the code is organized.
public boolean foo(... omitted ...) {
try {
synchronized (... omitted ...) {
if (... omitted ...) {
(omitted)
} else {
(omitted)
}
for (... omitted ...) {
if (... omitted ...) {
if (... omitted ...) {
if (... omitted ...) {
if (... omitted ...) {
if (... omitted ...) {
for (... omitted ...) {
(... omitted ...)
}
}
}
} else {
if (... omitted ...) {
for (... omitted ...) {
if (... omitted ...) {
(... omitted ...)
} else {
(... omitted ...)
}
if (... omitted ...) {
(... omitted ...)
} else {
if (... omitted ...) {
(... omitted ...)
}
}
if (... omitted ...) {
// ... etc. (we've seen enough!)
Okay, but what was it that made it the worst?
(When logged in, completion status appears here.)