24 KiB
Create readable code with conventions, whitespace, and comments in C#
Write code that is easier to read, update and support using naming conventions, comments and whitespace.
Learning objectives
In this module, you will:
- Choose descriptive names for variables that describe their purpose and intent.
- Use code comments to temporarily instruct the compiler to ignore lines of code.
- Use code comments to describe higher-level requirements or purpose for a passage of code.
- Write code that effectively uses whitespace to convey the relationship of lines of code.
Prerequisites
- Experience declaring variables using common data types.
- Experience using
Console.WriteLine()
to output messages to a console. - Experience using the
if
andelse if
statement.
Introduction
The code that you write should communicate your intent to both the compiler and other developers who may need to read your code. And since you're the developer who will be reading your code most often, sometimes months after you originally wrote it, it's in your best interest to write code that's clear and easy to understand. Remember, you may write code once, but you will need to read it many times.
Suppose you've been asked to write some code for another group of developers. You meet with them to discuss the specification and the assignment is clear. After the meeting they tell you that you'll be working independently during development. Once you're done, you'll hand off your code to the other group. The coding task isn't beyond your skill level, but you've never had to write code that someone else will be maintaining. The team told you that as long as you to follow the standard coding conventions for C#, there should be no problem. You make plans to review the C# coding conventions that relate to the code you're going to work on.
In this module, you'll learn how to choose names for your variables that describe their purpose and intent. You'll learn how to add code comments that document the higher-level requirements and your approach in code, as well as to temporarily instruct the compiler to ignore lines of code. Finally, you'll learn how whitespace can be used to help convey the relationship of individual lines of code.
By the end of this module, you'll write code more purposefully, focusing on the readability and quality of the code to communicate to both the compiler and other developers.
Choose variable names that follow rules and conventions
A software developer once famously said, "The hardest part of software development is naming things." Not only does the name of a variable have to follow certain syntax rules, it should also be used to make the code more human-readable and understandable. That's a lot to ask of one line of code!
Variable name rules
There are some variable naming rules that are enforced by the C# compiler.
- Variable names can contain alphanumeric characters and the underscore (
_
) character. Special characters like the pound#,
the dash-,
and the dollar sign$
are not allowed. - Variable names must begin with an alphabetical letter or an underscore, not a number. Using an underscore character to start a variable name is typically reserved for private instance fields. A link to further reading can be found in the module summary.
- Variable names must NOT be a C# keyword. For example, these variable name
declarations won't be allowed:
float float;
orstring string;
. - Variable names are case-sensitive, meaning that
string MyValue;
andstring myValue;
are two different variables. - Variable name conventions
Conventions are suggestions that are agreed upon by the software development community. While you're free to decide not to follow these conventions, they're so popular that it might make it difficult for other developers to understand your code. You should practice adopting these conventions and make them part of your own coding habits.
- Variable names should use camel case, which is a style of writing that uses
a lower-case letter at the beginning of the first word and an upper-case
letter at the beginning of each subsequent word. For example:
string thisIsCamelCase;
. - Variable names should be descriptive and meaningful in your application. You
should choose a name for your variable that represents the kind of data it
will hold (not the data type). For example:
bool orderComplete;
, NOTbool isComplete;
. - Variable names should be one or more entire words appended together. Don't
use contractions because the name of the variable may be unclear to others who
are reading your code. For example:
decimal orderAmount;
, NOTdecimal odrAmt;
. - Variable names shouldn't include the data type of the variable. You might
see some advice to use a style like
string strMyValue;
. It was a popular style years ago. However, most developers don't follow this advice anymore and there are good reasons not to use it.
The example string firstName; follows all of these rules and conventions, assuming I want to use this variable to store data that represents someone's first name.
Variable name examples
Here's a few examples of variable declarations (using common data types):
char userOption;
int gameScore;
float particlesPerMillion;
bool processedCustomer;
Other naming conventions
The rules and conventions described above are for local variables. A local variable is a variable that is scoped within the body of a method, or a variable in a console application that uses top-level statements (like the code in this module).
There are other types of constructs that you can use in your applications, and many have their own conventions. For example, classes are often used in C# programming, and have associated conventions. Although you won't be creating classes in this module, it's important for you to know that the naming conventions you just learned about fit into a larger naming framework.
Exercise
Create effective code comments
In this exercise, you'll add notes to your code and temporarily disable certain lines of code from compilation. Then you'll look at how the C# compiler understands whitespace and how to use whitespace to increase the readability of your code.
What is a code comment?
A code comment is an instruction to the compiler to ignore everything after the code comment symbols in the current line.
// This is a code comment!
This may not seem useful at first, however it's useful in three situations:
- When you want to leave a note about the intent of a passage of code. It can be helpful to include code comments that describe the purpose or the thought process when you're writing a particularly challenging set of coding instructions. Your future self will thank you.
- When you want to temporarily remove code from your application to try a different approach, but you're not yet convinced your new idea will work. You can comment out the code, write the new code, and once you're convinced the new code will work the way you want it to, you can safely delete the old (commented code).
- Adding a message like
TODO
to remind you to look at a given passage of code later. While you should use this judiciously, it's a useful approach. You may be working on another feature when you read a line of code that sparks a concern. Rather than ignoring the new concern, you can mark it for investigation later.
Note
Code comments should be used to say what the code cannot. Often, developers update their code but forget to update the code comments. It's best to use comments for higher-level ideas and not to add comments about how an individual line of code works.
At the Terminal command prompt, to create a new console application in a
specified folder, type dotnet new console -o ./PathTo/Project
and then press
Enter.
Create and use code comments
In this task, you will try creating and removing various types of code comments.
Enter the following code:
string firstName = "Bob";
int widgetsSold = 7;
Console.WriteLine($"{firstName} sold {widgetsSold} widgets.");
To modify your code with code comments and revisions, update your code as follows:
string firstName = "Bob";
int widgetsPurchased = 7;
// Testing a change to the message.
// int widgetsSold = 7;
// Console.WriteLine($"{firstName} sold {widgetsSold} widgets.");
Console.WriteLine($"{firstName} purchased {widgetsPurchased} widgets.");
Take a minute to review your comments and code updates.
Notice that the code comments are used to document the potential change being made, and to temporarily disable the old message as you test the new message. Your next step will be to test your update. If you're satisfied with the new code, you can safely delete the old code that was commented out. This is a safer, more methodical approach to modifying working code until you're convinced that you're ready to permanently remove it.
At the Terminal command prompt, type dotnet run and then press Enter.
You should see the following output:
Bob purchased 7 widgets.
Again, if you're satisfied with your updates, delete the old code that's commented out.
Delete the code comments.
Your code should match the following:
string firstName = "Bob";
int widgetsPurchased = 7;
Console.WriteLine($"{firstName} purchased {widgetsPurchased} widgets.");
To apply a block comment that comments out multiple lines, update your code as follows:
/*
string firstName = "Bob";
int widgetsPurchased = 7;
Console.WriteLine($"{firstName} purchased {widgetsPurchased} widgets.");
*/
Block comments are great if you need to write a long comment or remove many
lines of code. Block comments use a /*
at the beginning of the code and a
*/
at the end. Using a block comment is the quickest and easiest way to
disable three or more lines of code.
Replace your existing code with the following:
Random random = new Random();
string[] orderIDs = new string[5];
// Loop through each blank orderID
for (int i = 0; i < orderIDs.Length; i++){
// Get a random value that equates to ASCII letters A through E
int prefixValue = random.Next(65, 70);
// Convert the random value into a char, then a string
string prefix = Convert.ToChar(prefixValue).ToString();
// Create a random number, pad with zeroes
string suffix = random.Next(1, 1000).ToString("000");
// Combine the prefix and suffix together, then assign to current OrderID
orderIDs[i] = prefix + suffix;
}
// Print out each orderID
foreach (var orderID in orderIDs){
Console.WriteLine(orderID);
}
Note
There are many C# concepts in this code listing that may be new to you. It's not necessary to understand what the code is doing in order to appreciate how comments can help readers understand the purpose of the code.
Take a minute to see if you can figure out the purpose of the code.
Given the comments, you might be able to figure out what the code is doing (assuming the comments accurately describe the current state and were updated as the code was updated). But can you guess why this code exists? Wouldn't it be helpful if there was some explanation at the top of the code file that provided some context and described its purpose?
Consider how you would improve the comments.
Notice that there are two main problems with these comments:
- The code comments unnecessarily explain the obvious functionality of individual lines of code. These are considered low-quality comments because they merely explain how C# or methods of the .NET Class Library work. If the reader is unfamiliar with these ideas, they can look them up using learn.microsoft.com or IntelliSense.
- The code comments don't provide any context to the problem being solved by the code. These are considered low-quality comments because the reader doesn't gain any insight into the purpose of this code, especially as it relates to the larger system.
Remove the existing comments.
Your code should match the following:
Random random = new Random();
string[] orderIDs = new string[5];
for (int i = 0; i < orderIDs.Length; i++){
int prefixValue = random.Next(65, 70);
string prefix = Convert.ToChar(prefixValue).ToString();
string suffix = random.Next(1, 1000).ToString("000");
orderIDs[i] = prefix + suffix;
}
foreach (var orderID in orderIDs){
Console.WriteLine(orderID);
}
Notice that the code is already less cluttered.
To add a comment that explains the higher-level purpose of your code, update your code as follows:
/*
The following code creates five random OrderIDs
to test the fraud detection process. OrderIDs
consist of a letter from A to E, and a three
digit number. Ex. A123.
*/
Random random = new Random();
string[] orderIDs = new string[5];
for (int i = 0; i < orderIDs.Length; i++){
int prefixValue = random.Next(65, 70);
string prefix = Convert.ToChar(prefixValue).ToString();
string suffix = random.Next(1, 1000).ToString("000");
orderIDs[i] = prefix + suffix;
}
foreach (var orderID in orderIDs){
Console.WriteLine(orderID);
}
A comment's usefulness is subjective. In all matters related to code readability, you should use your best judgment. Do what you think is best to improve the clarity of your code.
Recap
The main takeaways from this exercise:
- Use code comments to leave meaningful notes to yourself about the problem your code solves.
- Don't use code comments that explain how C# or the .NET Class Library works.
- Use code comments when temporarily trying alternative solutions until you're ready to commit to the new code solution, at which point you can delete the old code.
- Never trust comments. They may not reflect the current state of the code after many changes and updates.
Exercise
Use whitespace to make your code easier to read
Print and web designers understand that putting too much information in a small space overwhelms the viewer. So, they strategically use whitespace, or negative space, to break up information to maximize the viewer's ability to consume the primary message of their work.
Developers can use a similar strategy when writing code in an editor. By using white space to convey meaning, developers can increase the clarity of their code's intent.
What is whitespace?
The term "whitespace" refers to individual spaces produced by the space bar
,
tabs produced by the tab
key, and new lines produced by the enter
key.
The C# compiler ignores whitespace. To understand how whitespace is ignored, and how to maximize clarity using white space, work through the following exercise.
Add code to illustrate how whitespace is ignored by the C# compiler
Enter the following code:
// Example 1:
Console
.
WriteLine
(
"Hello Example 1!"
)
;
// Example 2:
string firstWord="Hello";string lastWord="Example 2";Console.WriteLine(firstWord+" "+lastWord+"!");
At the Terminal command prompt, type dotnet run
and then press Enter.
You should see the following output:
Hello Example 1!
Hello Example 2!
Take a minute to consider what this result tells you about how you should use whitespace in your code.
These two code examples illustrate two vital ideas:
- Whitespace doesn't matter to the compiler. However ...
- Whitespace, when used properly, can increase your ability to read and comprehend the code.
You likely write your code once, but need to read the code multiple times. Therefore, you should focus on the readability of the code you write. Over time, you'll get a feel for when and how to use whitespace, such as the space character, tabs, and new lines.
Early guidance:
- Each complete command (a statement) belongs on a separate line.
- If a single line of code becomes long, you can break it up. However, you should avoid arbitrarily splitting up a single statement to multiple lines until you have a good reason to do so.
- Use a space to the left and right of the assignment operator.
Replace your existing code with the following code:
Random dice = new Random();
int roll1 = dice.Next(1, 7);
int roll2 = dice.Next(1, 7);
int roll3 = dice.Next(1, 7);
int total = roll1 + roll2 + roll3;
Console.WriteLine($"Dice roll: {roll1} + {roll2} + {roll3} = {total}");
if ((roll1 == roll2) || (roll2 == roll3) || (roll1 == roll3)) {
if ((roll1 == roll2) && (roll2 == roll3)) {
Console.WriteLine("You rolled triples! +6 bonus to total!");
total += 6;
} else {
Console.WriteLine("You rolled doubles! +2 bonus to total!");
total += 2;
}
}
Notice that this code doesn't include much whitespace. This code will be used to illustrate an approach for adding whitespace to your applications. Effective whitespace should make it easier to understand what your code is doing.
Note
The code uses theRandom
class to help develop a simulated dice game, where the total value from three rolls is used to evaluate a "winning" score. The code awards extra points for rolling doubles or triples. You don't need to fully understand this code in order to see the benefit of including whitespace.
Take a minute to consider how you would use whitespace to improve the readability of this code.
There are two features of this code to take note of:
- There's no vertical whitespace in this code example. In other words, there's no empty lines separating the lines of code. It all runs together into one dense code listing.
- The code blocks as defined by the opening and closing curly brace symbols
{ }
are compressed together, making their boundaries difficult to visually discern.
Generally speaking, to improve readability, you introduce a blank line between two, three, or four lines of code that do similar or related things.
Phrasing your code using vertical whitespace is subjective. It's possible two developers won't agree on what is most readable, or when to add whitespace. Use your best judgment.
To add vertical whitespace that improves readability, update your code as follows:
Random dice = new Random();
int roll1 = dice.Next(1, 7);
int roll2 = dice.Next(1, 7);
int roll3 = dice.Next(1, 7);
int total = roll1 + roll2 + roll3;
Console.WriteLine($"Dice roll: {roll1} + {roll2} + {roll3} = {total}");
if ((roll1 == roll2) || (roll2 == roll3) || (roll1 == roll3)) {
if ((roll1 == roll2) && (roll2 == roll3)) {
Console.WriteLine("You rolled triples! +6 bonus to total!");
total += 6;
} else {
Console.WriteLine("You rolled doubles! +2 bonus to total!");
total += 2;
}
}
Your first line of whitespace is used to separate the declaration of the dice
variable from the code lines used to assign values to your roll variables.
This separation makes it easier to see how dice
is being used in your code.
Your next line of whitespace separates the declaration of your roll variables
from the declaration of total
. Grouping the declaration of your three roll
variables is helpful in two ways. First, it creates a group of code lines that
includes related variables. Second, the variable names are so similar and the
declaration follows the same pattern. So, grouping them together draws your
eye to the similarities and helps to expose the differences.
Finally, your third line of whitespace separates another group of related
statements from your nested if
statements. The group of statements that
includes the declaration of total
and the Console.WriteLine()
method is
related by purpose rather than appearance. Your code is focused on the total
value achieved by the three dice and whether the roll included doubles or
triples. These lines are related because you need to calculate total
and
report the results of the roll to the user.
Some developers might argue that you should add an empty line in between the
declaration of total
and the Console.WriteLine()
. Again, the choice of
whitespace is up to your best judgment. You should decide which is more
readable for you and use that style consistently.
All that you have left is the if
statement. You can examine that now.
Focusing on the lines of code below the if
keyword, modify your code as follows:
Random dice = new Random();
int roll1 = dice.Next(1, 7);
int roll2 = dice.Next(1, 7);
int roll3 = dice.Next(1, 7);
int total = roll1 + roll2 + roll3;
Console.WriteLine($"Dice roll: {roll1} + {roll2} + {roll3} = {total}");
// HORRIBLE
if ((roll1 == roll2) || (roll2 == roll3) || (roll1 == roll3))
{
if ((roll1 == roll2) && (roll2 == roll3))
{
Console.WriteLine("You rolled triples! +6 bonus to total!");
total += 6;
}
else
{
Console.WriteLine("You rolled doubles! +2 bonus to total!");
total += 2;
}
}
Notice that you've moved the opening and closing curly braces to their own line to improve spacing.
The {
and }
symbols create code blocks. Many C# constructs require code
blocks. These symbols should be placed on a separate line so that their
boundaries are clearly visible and readable.
Furthermore, it's important to use the tab
key to line up the code block
symbols under the keyword they belong to. For example, notice the line of code
that starts with the keyword if
. Below that line is the {
symbol. This
alignment makes it easy to understand that the {
"belongs to" the if
statement. Furthermore, the last }
symbol lines up with the if
statement
as well. The combination of alignment and indentation makes it easy to
understand where the code block begins and ends.
The code lines inside of this code block are indented, indicating that they "belong" to this code block.
You follow a similar pattern with the inner if
statement and else
statement,
and the code inside of those code blocks.
Not everyone agrees with this style guidance for including whitespace. However, you should consider using this guidance as a starting point when writing code. In the future, you can be purposeful when making a decision to deviate from this guidance.
Recap
The main takeaways from this exercise:
- Use whitespace judiciously to improve the readability of your code.
- Use line feeds to create empty lines to separate phrases of code. A phrase includes lines of code that are similar, or work together.
- Use line feeds to separate code block symbols so that they are on their own line of code.
- Use the tab key to line up a code block with the keyword they're associated with.
- Indent code inside of a code block to show ownership.
Exercise
Complete a challenge activity to improve code readability
Code challenges will reinforce what you've learned and help you gain some confidence before continuing on.
Code readability challenge
In this challenge, you'll use the techniques you learned in this module to improve the readability of a code sample. You are provided with a code sample that is poorly styled and commented. Your goal is to update the code using style guidelines for variable names, code comments, and whitespace to improve code readability.
Apply style guidelines to improve readability
string str = "The quick brown fox jumps over the lazy dog.";
// convert the message into a char array
char[] charMessage = str.ToCharArray();
// Reverse the chars
Array.Reverse(charMessage);
int x = 0;
// count the o's
foreach (char i in charMessage) { if (i == 'o') { x++; } }
// convert it back to a string
string new_message = new String(charMessage);
// print it out
Console.WriteLine(new_message);
Console.WriteLine($"'o' appears {x} times.");
Note
This code sample may include .NET Class Library methods that are unfamiliar to you. For example, you may not be familiar with theToCharArray()
method of theString
class, or theReverse
method of theArray
class. You do not need to fully understand the code sample in order to be successful in this challenge.
Tip
The high-level purpose of this code is to reverse a string and count the number of times a particular character appears.
To improve readability, update the code using style guidelines.
Use the techniques that you learned in this module to make improvements to the code and increase its readability.
/*
This code reverses a message, counts the number of times
a particular character appears, then prints the results
to the console window.
*/
string orig_msg = "The quick brown fox jumps over the lazy dog.";
char[] msg = orig_msg.ToCharArray();
Array.Reverse(msg);
int ltr_count = 0;
foreach (char ltr in msg) {
if (ltr == 'o') {
ltr_count++;
}
}
string new_message = new String(msg);
Console.WriteLine(new_message);
Console.WriteLine($"'o' appears {ltr_count} times.");