Levels of Understanding

This is going to be a story of how I learn new things and come to understand them well enough to put that knowledge into practice. I imagine these levels of understanding are similar for everyone, but I can't be sure. I'm just going off of my own experience. It's possible that for any given person or any given skill, learning could stop at any point where they've gleaned enough usefulness and the desire to continue learning has been exhausted.

However, if that desire to learn is strong, learning can develop through three main levels of understanding. The first level is the basic what to do to accomplish a task. The second level delves deeper into how things work in context to solve problems in the domain. The third and final level explores why things work the way they do. The answers to those three simple questions: what, how, and why encompass all of the understanding that can be gained from any skill, subject, or domain. To ground this discussion we'll focus on programming to make things more concrete. What is involved in each of these levels of understanding when learning how to program?

Levels of understanding

What Will Make it Work?


When first learning a new skill, we simply want to get something working right away. We want to see results, and know that we can produce those results without too much effort. What do we need to know to make it work? This is as true in programming as in anything else, and it's why tutorials are so popular. Tutorials walk you through what you have to do to get a new language, a new framework, or a new system to do something, anything, with a high chance of success. It doesn't provide much in the way of rationale or options, but it allows you to see something working and borne of your own effort.

In the first stages of learning how to program, questions are filled with 'whats.' What are the different language features, like literals, control statements, functions, classes, delegates, etc? These features are introduced starting with the simple ones and building up to more complex concepts. What is the correct syntax? A large amount of time is spent negotiating with the compiler or interpreter on the right way to format program statements so that your program will actually execute. What is the sequence of instructions that will make the program do what it's supposed to do? When the compiler is not being utterly difficult with cryptic error messages, this is where most of the programmer's time is spent—debugging their program and trying to get it to work correctly.

The questions of 'what' form the basis for all future knowledge on the subject. Without knowing what is available and what is possible, all other questions don't have anything to hang on and would just confuse matters until the basics are better understood. I find this to be true even now, after decades of programming. If I'm learning a new framework or a new language, I have to learn what's available and what the correct syntax is for a large portion of the system before any more complex concepts start to make any sense. It's like a fog surrounds the system, making everything opaque until I learn most of what's available and the fog starts to lift. The structure of the system starts to take shape, details become clearer, and I'm able to better understand the complexities of the system.

Knowing the lay of the land also helps later on because when I come to a new problem, I'm at least aware of various possibilities when trying to form a solution. I may remember some feature of the language or framework that could enable a neat way to solve the problem, and I just have to go look up how to use it. Learning the 'whats' of a new skill is certainly necessary to gain a working knowledge to be able to solve basic problems, but it would be a shame to stop there. We would be missing out on a vast amount of understanding if we stopped at what.

How Does it Work?


Learning how something works is a deeper level of understanding than simply knowing what is required to make it work. Only knowing what to do leaves it as a black box, but exploring how it works is like peaking inside the box and figuring out what's in there. Once you know how that black box works on the inside, you can start extrapolating from how it works under one set of conditions to how it would work if you changed those conditions. Now you can apply your tool to new problems in new situations that you may not have thought about before. You also have a much better understanding of how to use it at all, and what ways you can use it so that it will still work.

If the black box is a programming language, then one way of looking inside the box is to learn how the compiler or interpreter works to build and execute the statements of a program. Once you know how the compiler scans and parses the code, builds a symbol table and an abstract syntax tree, generates machine code, and links and executes the code, you have a far better understanding of what you can do with a programming language and how it all works together so that a program will do what you want it to do.

When you really learn how functions work and how languages capable of functional programming work, entirely new solutions become available for solving complex problems cleanly and elegantly. You learn how to pass functions around just like other parameters so that you can abstract away choices that would otherwise require lengthy and redundant if-else statements or case statements. Instead of hard-coding the steps of a function, you can pass in the steps that should be used as addition function arguments. These benefits, of course, are only the beginning of what you can do in functional programming languages.

Likewise, learning how classes and objects work in object-oriented languages opens up all kinds of options for organizing large, complex systems into more easily understood programs. When first learning about classes, concepts like inheritance, polymorphism, and encapsulation can be confusing, but once you understand how they work, it becomes clear that it's all about different ways of organizing a program, connecting data and methods for operating on that data, and getting it all to work as an integrated system.

We can even dig deeper into the workings of this black box to learn how the computer system actually executes the machine code that makes up a program. Learning how a program is loaded from disk into memory before it runs, how I/O works in all of its various forms, how the memory hierarchy is organized, and how instructions are executed on a processor all help you understand the context in which a program runs. With this knowledge you have a better appreciation for the scale of different program design choices and what trade-offs are involved for performance optimizations. Knowing how a program executes on real hardware makes it easier to identify where micro-optimizations won't matter at all and where optimizing inner loops or finding a more efficient algorithm are truly essential.

The importance of learning how things work applies equally well to learning how algorithms work or learning how a software framework works. Once you know how efficient algorithms are constructed you can more easily build new algorithms for problems that don't have ready-made solutions, yet. If you have a better understanding of how your favorite software framework is put together, and how all of the different pieces work together to make a complete functional system, you can make far better use of that framework to solve new and interesting problems instead of more instances of the tutorial problems you learned in the beginning. Learning how something works is not the end, though. There is a deeper question waiting to be answered that will unlock the true potential of a tool.

Why Does it Work?


At some point along the road to learning something new, we start asking the question why? Why is this done this way? Why is this choice preferred over that choice? Why does this work the way that it does? Sometimes these questions are asked too early, and we're not ready to hear the answers. We can't assimilate them into our overall understanding and retain the rationale behind the best practices and the inner workings of a system. Once the other questions of what and how are well traveled, the reasons for why can be better understood, and then they have a big impact on our mastery of this new thing we're learning.

With programming, when we start out the reasons why things are done a certain way in a certain language or framework don't make much sense. After gaining a better understanding of how a feature works, the question of why to use it becomes easier to understand as well. Why are blocks such an elegant solution in Ruby? An answer isn't even sufficient unless it addresses how blocks encapsulate a function and put the code that's relevant to the caller of the function in close proximity to it. When we really understand the reason why they're so elegant, it's much easier to see where they'll be most useful and how best to structure Ruby code to make the best use of blocks.

As another example how about the reason why MVC frameworks work so well as an architecture for software with a user interface? Such a question can hardly even be asked without understanding how an MVC framework is structured and how the different pieces interact. Simply learning what code to write to get an MVC framework up and running isn't enough to truly understand how best to use it in different situations. Learning how the framework really works, how the data moves through it, and how code in each of the model, view, and controller works together leads to a much better understanding of why they are so useful. Then it becomes easier to apply that framework to other problems that are much different than the original tutorial problem that was used as an introduction to the tool.

Taking a step back to the basics of writing code in general, Clean Code by Robert C. Martin is all about the reasons why we would structure code a certain way or make basic decisions about naming variables and functions with care. The beginner probably doesn't care much about these things because they wouldn't understand why they would be so important. I think reading this type of book as a second programming book after the introductory how-to-program type of book wouldn't add much value, but for a developing programmer who's been learning for a few years and may have exposure to more than one language, the wisdom contained in it is invaluable.

Why is the ultimate question, and it builds on itself as well. Understanding the answer to one why question can lead to a still deeper why question. With enough determination, a chain of why's can be followed until the point is reached where there's no longer a good explanation. This is the edge of a new frontier of knowledge, and it's places like these that are the most worth exploring and the most growth and development happens. Finding places where the why's end and putting in the effort to discover good answers is where real expertise lives.


This process of going through the phases of what, how, and why can take different forms. Sometimes the path is short and quick from figuring out what to do to make something work and understanding why it works so that the knowledge can be put to novel uses. Learning a new programming language feature in a mature language that you already know could have this short path. Other times the path can be long and difficult, like when learning an entirely new field of software engineering. It can take years of dedicated study to progress through the what's, how's, and why's of machine learning,  embedded programming, or web development to reach a level of mastery when starting from square one. To keep moving ahead, learning is mostly a matter of asking the right questions and seeking the right answers.

No comments:

Post a Comment