ms_learn_csharp/010_conventions/010_csharp.md

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 and else 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; or string string;.
  • Variable names are case-sensitive, meaning that string MyValue; and string 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;, NOT bool 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;, NOT decimal 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 the Random 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 the ToCharArray() method of the String class, or the Reverse method of the Array 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.");