Hello, Error!

A story about learning, failing, and finding growth through broken code.

Hello, World!

The classic starting point for almost every programming language. Even full frameworks begin with some version of “Hello, World!” and from there, new developers dive into strings, arrays, integers, and the excitement of making things work.

The problem is that too often we go from “Hello, World!” straight into building code that only works on the happy path. Error handling becomes an obstacle rather than the structure our programs should rely on.

Instead of preparing for failure, we patch over it with print statements or manual fixes. I learned the hard way that avoiding proper error handling costs time, productivity, and focus. Many hours of my early development life could have been used more productively if I had learned how to handle errors correctly.

Connie’s First Script

Write-Output "Hello, World!"

Output:
Hello, World!

There is nothing wrong with this script. It will not fail, and it performs exactly as expected. But it is also limited. It is like waving to your blind neighbor. You mean well, but you are not building any real connection.

Adding Functionality

$UserName = Read-Host "What is your name?: "
Write-Output "Hello, $UserName!"

Output:
What is your name?: Connie
Hello, Connie!

Now we have some interaction, which is progress. But what if the input is invalid? We need to handle that.

No Bad Information

$UserName = Read-Host "What is your name?: "
if ($UserName -match "[\d!@#$%^&*()]") {
  Write-Host "That is not what we wanted! Give us a name."
}
Write-Output "Hello, $UserName"

Output:
What is your name?: Connie1
That is not what we wanted! Give us a name.
Hello, Connie1!

This prints both the error message and the greeting. That is not what we want. We need to stop the greeting when validation fails.

Basic Error Handling with If/Else

$UserName = Read-Host "What is your name?: "
if ($UserName -match "[\d!@#$%^&*()]") {
  Write-Host "That is not what we wanted! Give us a name."
} else {
  Write-Output "Hello, $UserName"
}

This is better, but as your scripts grow, you will find yourself surrounded by if/else statements. The logic becomes cluttered and difficult to maintain.

Adding More Functionality

$UserName = Read-Host "What is your name?: "
if ($UserName -match "[\d!@#$%^&*()]") {
  Write-Host "That is not what we wanted! Give us a name."
} else {
  Write-Output "Hello, $UserName"
}

$Birthday = Read-Host "What is your birthday, $UserName?: "

Output:
What is your name?: Connie1
That is not what we wanted! Give us a name.
What is your birthday, Connie1?:

The problem here is that the invalid name still moves forward into the next step of the program. We need a way to stop execution entirely when that happens.

Nesting Like Russian Dolls

$UserName = Read-Host "What is your name?: "
if ($UserName -match "[\d!@#$%^&*()]") {
  Write-Host "That is not what we wanted! Give us a name."
} else {
  Write-Output "Hello, $UserName"
  $Birthday = Read-Host "What is your birthday, $UserName?: "

  if ($Birthday -match "\d\d\d\d-\d\d-\d\d") {
    Write-Output "Totally righteous birthday, babe!"
  } else { 
    Write-Output "Invalid birthday!"
  }
}

Output:
What is your name?: Connie
Hello, Connie
What is your birthday, Connie?: December 1, 1969
Invalid birthday!

Now the code works, but the nesting is getting heavy. It is harder to read and maintain. The deeper your logic goes, the harder it becomes to debug or scale.

Try Catch, but with a Catch

$UserName = Read-Host "What is your name?: "
try {
  if ($UserName -match "[\d!@#$%^&*()]") {
    throw "That is not what we wanted! Give us a name."
  }
} catch {
  throw $_
} 
Write-Output "Hello, $UserName"

# Get the user birthday
$Birthday = Read-Host "What is your birthday, $UserName?: "
try {
  if ($Birthday -match "\d\d\d\d-\d\d-\d\d") {
    Write-Output "Totally righteous birthday, babe!"
  } else {
    throw "Invalid birthday!"
  }  
} catch {
  throw $_
} 

Output:
What is your name?: Connie1
Error: That is not what we wanted! Give us a name.

We removed the nesting, but now every section has its own try and catch. This hurts readability and adds repetitive code that becomes difficult to maintain.

Cleaner Handling

try {
  # Get the User Name
  $UserName = Read-Host "What is your name?: "
  if ($UserName -match "[\d!@#$%^&*()]") {
    throw "That is not what we wanted! Give us a name."
  }
  Write-Output "Hello, $UserName"

  # Get the User Birthday
  $Birthday = Read-Host "What is your birthday, $UserName?: "
  if ($False -eq ($Birthday -match "\d\d\d\d-\d\d-\d\d")) {
    throw "Invalid birthday!"
  }
  Write-Output "$Birthday is an amazing day to have been born!"
  
} catch {
  # Global Error Handling
  Write-Error "Could not continue the script. $_"

} finally {
  # Clean up tasks
  Write-Output "Exiting the script."
}

Output:
What is your name?: Connie1
Error: Could not continue the script. That is not what we wanted! Give us a name.
Exiting the script.

This version is slightly longer but much cleaner. It introduces centralized error handling, better structure, and a clear cleanup process at the end of the script.

Adding Functions

Function Get-UserName {
  $UserName = Read-Host "What is your name?: "
  if ($UserName -match "[\d!@#$%^&*()]") {
    throw "That is not what we wanted! Give us a name."
  }
  return $UserName
}

Function Get-Birthday {
  $Birthday = Read-Host "What is your birthday, $UserName?: "
  if ($False -eq ($Birthday -match "\d\d\d\d-\d\d-\d\d")) {
    throw "Invalid birthday!"
  }
  return $Birthday
}

try {
  # Get the User Name
  $UserName = Get-UserName
  Write-Output "Hello, $UserName"

  # Get the User Birthday
  $Birthday = Get-Birthday
  Write-Output "$Birthday is an amazing day to have been born!"
  
} catch {
  Write-Error "Could not continue the script. $_"

} finally {
  Write-Output "Exiting the script."
}

Output:
What is your name?: Connie1
Error: Could not continue the script. That is not what we wanted! Give us a name.
Exiting the script.

By using functions, we have separated the logic of the script from the data collection. This makes it cleaner, easier to read, and simpler to debug. The logic section of the script is now shorter and more focused.

Previous
Previous

We Built the Internet on Open Source. Now It’s Being Sold Back to Us.

Next
Next

Despite All My VS Code, I’m Still Just a RAT in a Cage