Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C#

How to Write Code to Solve a Problem, A Beginner's Guide

Rate me:
Please Sign up or sign in to vote.
5.00/5 (34 votes)
6 Oct 2020CPOL7 min read 50.8K   12   34
First steps in development: break it, break it again!
By the time you've read this, you will be an UberKoderz, just like me! No. No, you won't. But ... you will have one of the really important tools available to you to start writing code: "Breaking it into smaller bits". And no, I'm not an UberKoderz either.

Introduction

All the code here is in C# - but it's all pretty simple stuff, and the process is the same in any language. Just "bleep" over the brackets and semicolons if you don't understand them!

Recently, there was a question which involved a chunk of homework: write a method that reads a file and returns all the lines which are followed by a line containing three asterisks. And the code was terrible, even for a beginner - so bad I won't embarrass anyone by linking to it.

Quote:

Good day everyone, I don't know if there is an option after the return method to continue or if I should otherwise construct a code. I would like to after the return I continue to the end of the txt file and let the return list more values.

C#
public static string FindLineAboveAsterisks(TextReader reader)
        {
            StringBuilder sbBuilder = new StringBuilder();
            string result = reader.ReadLine();
            string line = String.Empty;          

            while  (result is object && (line = reader.ReadLine()) is object)
        {
                int startIndex = 21;
                int length = 9;

            if (line.Contains("***"))
            {
                sbBuilder.AppendLine(result);
                return result;
                    
                }
                {
                    result = line.Substring(startIndex, length);
                }
                
            }
            return string.Empty;

You look at that code and you just start to wonder ... Why? What is that indentation? What is that there for? Why do this? How you do expect that to work?

And of course, it doesn't. It can't work - and the reason why is the author just threw it together without thinking about the task at all.

My Answer (Expanded a Little)

That looks like it was thrown together without any thought about what you are trying to do!

Blunt, I know. But I wanted to get his attention.

Throw it away, and think about your task: Read a file, find all the lines which are above lines with an asterisk, and return them.

Still blunt, but let's think about it.

So let's take it from the top: You need to return more than one line - so the obvious thing to do is to return a collection of strings instead of a single string. Because although you can return them as a single string, it makes life a lot harder for the code that calls your method - it has to "break it back up again" in order to use the information.
Let's change that:

C#
public static List<string> FindLineAboveAsterisks(TextReader reader)

Now, it returns a collection of strings so the outside world can work with it.

Think about what you are trying to get the method to do: don't complicate things for the calling code - because you are going to call it one or more times, and you will write it once. If you make the outside world work harder, then you are just adding work you have to do every time you use the method.
So if you need a collection of items, return a collection - don't bodge round so that the outside world has to do more processing each time it calls you!

But ... why are you passing it a TextReader? That means each time you call it, the outside world has to do the work of creating, opening, passing, and closing the reader - which is silly. Pass the path instead, and let the method do what it wants with it:

C#
public static List<string> FindLineAboveAsterisks(string filePath)

Now, the caller looks easier to work with.

Again, make life easier for yourself: you want to read a file? Pass the path and let the method decide what to do with it. If you pass a TextReader, or a Stream, then you are limiting what the outside world can do, and forcing a "shape" on code that may not be the easiest or most efficient for the job it has to do.
The more "generic" you make parameters, then more flexible your code can be - and that means it can be reused - which saves you writing another, similar method to do much the same thing.

Let's start filling in the method: We need a List to return, and to process every line in a file. If we want to use every line, then let's just get them all and let the system handle it! That's pretty easy:

C#
public static List<string> FindLineAboveAsterisks(string filePath)
    {
    List<string> lines = new List<string>();
    foreach (string line in File.ReadLines(filePath))
        {
        // ...
        }
    return lines;
    }
What could be simpler? We know there are two things we must do: return a collection of lines, and process all the lines in a file. So create the collection at the top of the method; return it at the end. Add a simple loop to give us each line at a time. Result: the code is simple, and easy to write. And if it's easy to write, it's probably going to work ...

Now, what do we have to do with the lines?
Simple; we need to collect all the lines where the next line contains three asterisks.
So we need to know what the last line was.

Think about it for a moment: inside the loop, how can we tell what the next line contains? Practically speaking, we can't (unless we complicate the code and use a different looping construct, but that's messy). What we do know though is what the previous line was - because we have already processed it and can keep a copy for next time.
So turn the problem on its head and think of it as "find all the lines which contain three asterisks, and return the previous line for each". A moments thinking tells you that gives the same result, and means we can work with "historical data" which we have already looked at instead of "future data" that we haven't.

Let's add that:

C#
public static List<string> FindLineAboveAsterisks(string filePath)
    {
    List<string> lines = new List<string>();
    string lastLine = "";
    foreach (string line in File.ReadLines(filePath))
        {
        // ...
        lastLine = line;
        }
    return lines;
    }
Each time, we are adding a tiny amount of simple code - nothing complicated, so there is less to go wrong.

We need to check if the current line contains "***". If it does, add the last one to the collection. That's easy too - a quick if test will do it:

C#
public static List<string> FindLineAboveAsterisks(string filePath)
    {
    List<string> lines = new List<string>();
    string lastLine = "";
    foreach (string line in File.ReadLines(filePath))
        {
        if (line.Contains("***"))
            {
            lines.Add(lastLine);
            }
        lastLine = line;
        }
    return lines;
    }

Hang on ... it's finished, isn't it?

All we have to do now is call it and test it:

C#
string path = @"D:\Test Data\List of hats.txt";
foreach (string line in FindLineAboveAsterisks(path))
    {
    Console.WriteLine(line);
    }
I could have shown you the original code for that ... but you might have just finished eating ...

Oh look - it works!

So What Did We Do?

Basically, all we did was take a whole task and break it into littler ones:

Quote:

Write a method that reads a file and returns all the lines which are followed by a line containing three asterisks.

  1. Decide what it needs to return
  2. Decide what it needs as parameters
  3. Create the returnable value, and set us up to return it.
  4. Add a loop to look at each line.
  5. Save the current line for next time at the end of the loop, when we are finished looking at it.
  6. Check if the line has asterisks.
  7. If so, add the line we saved last time round the loop to our output collection.

None of those tasks are difficult: they are a line or two of code and it's pretty simple code as well.

And that's the secret: big tasks are made up of smaller ones, and those are made of even smaller ones.

You are used to that: you use it every day!

Task: "Have breakfast."

Smaller tasks:

  1. Go to kitchen.
  2. Decide what to eat for breakfast.
  3. Prepare it.
  4. Eat it.
  5. Wash up after yourself.

Each of those tasks may be quite complicated:

Subtask: "Go to kitchen"

  1. Work out where you are.
  2. Work out how to get from here to the kitchen.
  3. Move there.

Those may have sub-sub-sub tasks:

Sub-sub-task: "Work out where you are"

  1. Wake up.
  2. Open eyes.
  3. Look around: where am I? Do I recognise this room? What the hell did I do last night?
  4. ...

Summary

The point is that every task can be broken down into smaller pieces until you reach a task you either can do, or know how to find out how to do. If you wake up in a strange room, then you need to check for other people, and maybe ask them where the kitchen is - and so on.

Software tasks are the same; refine the task into smaller bits and some - probably all - can be done easily, and build up to completing bigger, more complex tasks that sound impossible.

Just start by thinking instead of jumping into code: five minutes planning can save you hours of work!

History

  • 6th October, 2020: Original version
  • 6th October, 2020: Typos. Always typos ...
  • 8th October, 2020: A couple of less than and greater than symbols were fixed: they were showing as the HTML equivalent: "&lt;" and "&gt;"

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO
Wales Wales
Born at an early age, he grew older. At the same time, his hair grew longer, and was tied up behind his head.
Has problems spelling the word "the".
Invented the portable cat-flap.
Currently, has not died yet. Or has he?

Comments and Discussions

 
PraiseParsing lines from a file / Nice logic / Thank you Pin
Member 1507871611-Jul-22 6:13
Member 1507871611-Jul-22 6:13 
GeneralMy vote of 5 Pin
Member 1507871611-Jul-22 6:10
Member 1507871611-Jul-22 6:10 
PraiseA good introduction Pin
colins215-Feb-22 23:54
colins215-Feb-22 23:54 
GeneralMy vote of 5 Pin
Eugene Rutsito5-Sep-21 5:34
Eugene Rutsito5-Sep-21 5:34 
GeneralMy vote of 5 Pin
Stefan_Lang26-Apr-21 22:03
Stefan_Lang26-Apr-21 22:03 
GeneralMy vote of 5 Pin
Maciej Los13-Apr-21 9:03
mveMaciej Los13-Apr-21 9:03 
QuestionRecommended edit Pin
tarco23-Feb-21 14:13
professionaltarco23-Feb-21 14:13 
GeneralMy vote of 5 Pin
MarcusCole68333-Feb-21 10:59
professionalMarcusCole68333-Feb-21 10:59 
GeneralMy vote of 5 Pin
Grumpus19-Nov-20 6:53
Grumpus19-Nov-20 6:53 
QuestionDefinitely not agreeing Pin
Andreas Saurwein17-Nov-20 3:13
Andreas Saurwein17-Nov-20 3:13 
GeneralMy vote of 1 Pin
Member 1499082112-Nov-20 1:45
Member 1499082112-Nov-20 1:45 
GeneralRe: My vote of 1 Pin
Andreas Saurwein17-Nov-20 3:13
Andreas Saurwein17-Nov-20 3:13 
QuestionWhat if the first line contains three stars? Pin
Haak11-Oct-20 23:56
Haak11-Oct-20 23:56 
GeneralAnother Way - Simple One Liner Pin
Member 148676327-Oct-20 3:33
Member 148676327-Oct-20 3:33 
GeneralRe: Another Way - Simple One Liner Pin
OriginalGriff7-Oct-20 4:09
mveOriginalGriff7-Oct-20 4:09 
GeneralRe: Another Way - Simple One Liner Pin
Member 148676327-Oct-20 4:58
Member 148676327-Oct-20 4:58 
GeneralRe: Another Way - Simple One Liner Pin
OriginalGriff7-Oct-20 5:12
mveOriginalGriff7-Oct-20 5:12 
GeneralRe: Another Way - Simple One Liner Pin
George Swan7-Oct-20 6:23
mveGeorge Swan7-Oct-20 6:23 
GeneralRe: Another Way - Simple One Liner Pin
Member 148676327-Oct-20 8:55
Member 148676327-Oct-20 8:55 
GeneralRe: Another Way - Simple One Liner Pin
Richard MacCutchan8-Oct-20 3:41
mveRichard MacCutchan8-Oct-20 3:41 
GeneralRe: Another Way - Simple One Liner Pin
Member 148676328-Oct-20 4:18
Member 148676328-Oct-20 4:18 
PraiseRe: Another Way - Simple One Liner Pin
Member 1507871611-Jul-22 6:31
Member 1507871611-Jul-22 6:31 
QuestionWhat about the single responsibility principle? Pin
George Swan6-Oct-20 22:16
mveGeorge Swan6-Oct-20 22:16 

Thanks for the excellent article, there‘s some great advice in it. Could I raise a couple of points? Is it ok to give the method two tasks to perform -loading data from a disc and then processing the data? Wouldn‘t it be better to have an IEnumerable<string> as the input parameter and give the method the single responsibility of processing the collection? It seems to me that there would still be the opportunity to accept many different types of collections and it would also allow testing to be carried out entirely in memory.


AnswerRe: What about the single responsibility principle? Pin
OriginalGriff7-Oct-20 4:17
mveOriginalGriff7-Oct-20 4:17 
GeneralRe: What about the single responsibility principle? Pin
George Swan7-Oct-20 20:34
mveGeorge Swan7-Oct-20 20:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.