The programming world is inundated with an overwhelming plethora of resources, tutorials, and courses for beginners. For the self-guided learner, it can be all too tempting to follow these guides verbatim, typing along as code unfolds in front of you. But the harsh reality is that merely replicating someone else’s code doesn’t equate to learning. To genuinely grasp the essence of programming and make the learning stick, you need to embrace errors, make as many mistakes as you can , and change everything. Let’s dive deeper into why this is such an effective learning strategy.
The Power of Mistakes
When you first start programming, seeing errors or bugs can feel like a nightmare, making you feel like nothing’s going your way. But trust me, making mistakes is an essential part of learning. Each error message that pops up is a new chance to learn. It pushes you to dig deeper, to understand how things really work. And when you learn like this, you’ll remember it for a long time. We tend to remember our mistakes… so I suggest you pair that sting of messing up (which sticks in your mind because it’s tied to a feeling) with a lesson that’ll last.
When you code along with a tutorial, you might not comprehend the intricacies of the code, what it does and the decision making behind those choices. However, when you deliberately step out of the line and encounter errors, you start exploring and understanding why things break, which can ultimately lead to a much deeper understanding of the concepts involved.
Changing Everything
When I talk about changing everything, I’m not suggesting you reinvent the wheel. Instead, I propose a straightforward method that could drastically enhance your understanding and retention of programming concepts. The method involves two main steps:
Changing Variable Names
Variable names are essential in any programming language. A well-named variable can make your code more readable and maintainable. It’s easy to just copy the variable names used in the tutorials, but when you change them, you engage with the code at a deeper level.
Let’s take a look at how it works. You’re following a tutorial and come across a variable with the creative name “name”. Instead of copying this directly, challenge yourself to refine it based on its role within the code. Does it represent a first name, a surname, or perhaps the full name? discerning the semantics of a variable and appropriately naming it can streamline your understanding and future interaction with the code. For example, encountering this refined variable later in the code, you’ll be able to instantly grasp its purpose without the need to debug or backtrack.
To shed some light on this approach, let’s examine a Python code excerpt from a hypothetical tutorial:
In a tutorial, you might encounter a function like this:
def greeting(name):
return f"Hello, {name}!"
In this function, “name” is somewhat descriptive, but it could be unclear. Is “name” a first name, a last name, or a full name? If you later see a name variable in a different part of the code, you might not immediately understand what it represents and how to treat it (for example if it’s a surname maybe you will want to add “Mr/Mrs.”)
By renaming the variable to something more explicit, you can avoid this confusion:
def greeting(full_name):
return f"Hello, {full_name}!"
Now, full_name clearly indicates that we expect the full name of the person to greet. This makes the code more readable and easier to understand, for your teammates and even your future developer self (which could be later in the same day). It’s a small change, but the practice of thoughtful naming can significantly enhance code clarity.
When you give your code a good shake and shuffle those variable names, suddenly, you’re not just coding along — you’re in the game, playing detective and tracing each variable’s footprints. It’s like looking at each line of code closely using a magnifying glass. The greatest thing, though? You’ll be able to quickly identify this variable the next time you run across it in a remote section of the project. You’ll give it a knowing nod, like two secret agents meeting in a crowded plaza. “Ah, full_name, we meet again. I know exactly why you’re here.” And just like that, your code is no longer a string of characters — it’s an unfolding narrative, a comedy of errors, a drama of bugs squashed and functions fulfilled.
Changing function names and breaking them down
Here’s an example of a vague function I saw in a tutorial:
function calculate(num1, num2) {
let total = num1 + num2;
let finalTotal = total + (total * 0.15);
return finalTotal;
}
Just like in the previous example, the function name “calculate” isn’t very descriptive and leaves us guessing its purpose. When you will see in another part of the project a function name calculate, you will have no idea what it does unless you will view it. Does it perform an addition, a subtraction, a complex computation? Also, it’s not immediately clear that it’s also calculating tax on top of summing two numbers.
An excellent approach to applying this practice is to adopt a habit of questioning. Each time you encounter a function or method, pause and ask yourself: “What is its purpose? What does it do?”
If you find yourself listing more than one responsibility, that’s a bingo! You’ve hit the jackpot. This is your golden opportunity to flex your refactoring muscles. Split that multifaceted function into smaller, purposeful functions. This not only deepens your understanding of the code at hand, but also cultivates an invaluable skill set that will elevate your coding craftsmanship to a new level.
first the we want a function the add numbers so an appropriate name would be:
function addNumbers(num1, num2) {
return num1 + num2;
}
the second function adds tax so it’s quite clear to write it like this:
function addTax(total) {
return total + (total * 0.15); // Adding a 15% tax
}
Using both functions together
let total = addNumbers(5, 10);
let finalTotal = addTax(total);
The addNumbers() and addTax() functions now have clear, self explanatory names, and each function fulfills a single purpose. This approach not only makes the code more readable but also improves modularity, facilitating easier testing and debugging. Each function can be tested independently, and if an error arises, pinpointing and resolving the issue becomes significantly more manageable.
In my opinion, clarity should always be prioritized over conciseness. It’s not about crafting the shortest possible code, but about writing code that’s easy to comprehend, debug, and maintain. Fun fact about these all the “clever” Python/JavaScript one liners snippets you see all over, usually they are not only unreadable they are also slower.
Let me now share a remarkable example that exemplifies the value of making errors in learning. The saying “The only thing scarier than code that doesn’t work is code that works but you have no idea why” is an old one that we frequently come across. Here’s an illustration of how these confusing times may lead to important educational opportunities.
I was in the thick of developing a web application, everything humming along smoothly, every feature behaving as expected. With an air of satisfaction, I decided it was time to give my code a neat little haircut, tidy up the strays and sharpen the overall structure. It was during this grooming session that I stumbled upon a variable named ‘grid’ (I know, very imaginative). An unexpected guest, as I had no recollection of introducing it to the party. Yet, here it was, not just sipping punch in the corner but actively involved in the dance, a major participant in the component’s operation.
In the process of refining my code, it seems I had accidentally ghosted ‘grid’, scrubbing all explicit references to it. Normally, such an oversight would set off a fireworks display of error messages. But to my surprise, my application was humming along happily, blissfully unaware of the phantom variable among its ranks. It was as if ‘grid’ was a secret agent, quietly fulfilling its mission behind the scenes.
A lightbulb suddenly went off in my head. I was wondering, could it be a strange coincidence that the variable ‘grid’ and the id of a div shared the same name? I googled something like this “javascript id name same as variable name”. And lo and behold, the search results unveiled a realm of knowledge I had yet to explore.
It turned out that in the magic land of JavaScript, assigning an id to an HTML element is akin to conjuring a global variable into existence. This magical variable shares the same name as the id, ready to fulfill its destiny without an explicit declaration. It was a fascinating revelation, a hidden nook in the vast library of JavaScript that I had yet to peruse.
you can read more and see examples in this fascinating article:
This accidental discovery lead me to learn about a concept called variable shadowing. If I would merely follow the tutorial verbatim, this rich learning experience could have easily slipped past me. But without doing the mistake and creating ‘grid’ by accident, led to a deeper understanding of web development.
By the way, variable shadowing occurs often across a wide range of computer languages, not only in JavaScript. To give you an example, variable shadowing occurs when two variables with the same name are defined in different scopes, such as within a function. The inner variable “shadows” or takes precedence over the outer variable when the code is executed. The inner variable, not the outer one, is referenced whenever that variable name is used within the scope of that variable.
To illustrate, consider a Python example:
x = 10 # outer (global) variable
def some_function():
x = 5 # inner (local) variable
print(x) # will print 5
some_function()
print(x) # will print 10
In this snippet, the x within some_function is shadowing the global x. So, when x is printed inside the function, it outputs 5, courtesy of the local variable overshadowing the global one. But when x is printed outside the function, it outputs 10, as the global x is accessible here.
My encounter with the grid variable in JavaScript is a unique instance of variable shadowing, where the global variables created by the browser to represent HTML elements with ids can be overshadowed by JavaScript variables. However, this general concept of variable shadowing is widespread in programming languages. This shows the importance of choosing clear and descriptive names for your variables. If I had chosen a more suited name like mainGrid or containerGrid, I would see the problem that the variable was not defined anywhere but I would miss a great opportunity to learn .
Another one? sure. Have you ever been on the receiving end of the console’s fiery wrath, faced with the terror-inducing, crimson-hued proclamation: “______ is not a function”? Oh, the panic that sets in during that first encounter! It feels like you’ve accidentally detonated a code bomb, causing a chaotic mess.
But as the shock wears off, you’ll often get to work trying to control the commotion. The issue frequently results from attempting to use a method in a context where it has no place, such as when using toLowerCase() to lowercase a number instead of a string.
You might think, “No biggie, I just used the wrong variable, hence the error.” True, but there’s more to glean from this situation. You should ask yourself why you weren’t aware of the variable type you were working with. If you expected a string, how did you end up with a number type? Or undefined? Or null?
These probing questions, inspired by seemingly minor errors, can guide you towards deeper comprehension. They help you uncover the assumptions you might have unknowingly made and understand the nuances of type handling in JavaScript.
Let’s see a simple example:
let myVariable = 1234;
try {
console.log(myVariable.toLowerCase());
} catch (error) {
console.log(error);
// logs: TypeError: myVariable.toLowerCase is not a function
}
In this example, you’ve attempted to use toLowerCase() on a number, which results in a TypeError. This mistake can lead you to explore JavaScript type handling and understand how to properly check or assert types before attempting operations. Thus, a small error paves the path for a big learning opportunity. It could be that you selected by mistake the user’s phone number instead of the user’s address, these things happen all the time. There are a lot of things you can do to handle these situations like error handling and validation before trying to do anything with a string.
So the next time the console bellows at you in red, don’t panic. Instead, treat it as a clue leading to a treasure trove of learning. Dive in, explore, and grow as a coder. Errors are not your enemy; they’re stepping stones on the path to mastering programming.
Final Thoughts
What I’m suggesting here is not just to wait for errors to happen, but to stir things up intentionally using the methods I’ve mentioned above. By tweaking variable names, breaking down functions, and straying off the beaten path of tutorials, you’re inviting a chance for mistakes to occur. Don’t worry, that’s a good thing! In the process of learning, these self-induced errors will provide a deeper understanding and enrich your learning experience. Remember, it’s all about flipping the narrative on errors — instead of viewing them as obstacles, consider them stepping stones on your coding journey.
Think about it like playing an RPG. For every challenge you beat, every mystery you unravel, and every mistake you fix, you earn experience points. It’s like doing lots of side quests before going head to head with the final boss (and in our case a picky client — just kidding). The more experience points you gather, the more capable you become, ready to navigate the twisted roads of programming. So, the next time an error message flashes on your screen, don’t shy away. Embrace it, analyze it, learn from it, and come out the other side stronger, wiser, and more skilled. It’s through these bumps and hurdles that you truly become a seasoned programmer.
Pingback: Three Hard Things in Computer Science - learningjournal.dev
Pingback: Stealth Skills in Frontend - learningjournal.dev