In the past few years, I have the luxury of working and mentoring with a considerable number of beginners. Although I have clearly witnessed a considerable part of my programming failure, things are not as black and white as they seem. Among beginners, I always see some patterns and behaviors. While some of these behaviors are misleading and harmful, many provide learning opportunities for senior developers. Contrary to popular belief, considerable lessons can be learned from people with less experience because they have an unbiased view (Beginner's mind).
Conditional inversion
One of the most common anti patterns I see with beginners is what I like to call "conditional inversion" (it may already have a better name). Conditional inversion may occur when using branch statements to control or restrict code flow. The following is an example of inverted Code:
<span style="color:var(--highlight-color)"><code><span style="color:var(--highlight-keyword)">function</span> <span style="color:var(--highlight-literal)">atleastOneOdd</span>(potentialOdds) { <span style="color:var(--highlight-keyword)">if</span> (potentialOdds !== <span style="color:var(--highlight-literal)">undefined</span>) { <span style="color:var(--highlight-keyword)">if</span> (potentialOdds.length) { <span style="color:var(--highlight-keyword)">return</span> potentialOdds.some((num) => num & <span style="color:var(--highlight-namespace)">1</span>); } } <span style="color:var(--highlight-keyword)">return</span> <span style="color:var(--highlight-literal)">false</span>; }</code></span>
The effect of exaggeration (but I must see worse)
If you think the above code is too complex for such a simple task, you are not alone. Perceived complexity is the result of nesting, Because nesting strongly contributes to the "perceived complexity" of code ". here is a functionally equivalent but much more readable implementation:
<span style="color:var(--highlight-color)"><code><span style="color:var(--highlight-keyword)">function</span> <span style="color:var(--highlight-literal)">atLeastOneOdd</span>(potentialOdds) { <span style="color:var(--highlight-keyword)">if</span> (potentialOdds === <span style="color:var(--highlight-literal)">undefined</span>) { <span style="color:var(--highlight-keyword)">return</span> <span style="color:var(--highlight-literal)">false</span>; } <span style="color:var(--highlight-keyword)">if</span> (!potentialOdds.length) { <span style="color:var(--highlight-keyword)">return</span> <span style="color:var(--highlight-literal)">false</span>; } <span style="color:var(--highlight-keyword)">return</span> potentialOdds.some((num) => num & <span style="color:var(--highlight-namespace)">1</span>); }</code></span>
Lesson:
In general, less nesting makes code easier to read and maintain. As with all rules, there are always exceptions, so be sure to understand the context before making a decision. Here is a great thread that covers this particular scenario in more depth:
Know when to watch
It's important to remember that it's not just beginners who know "less", but they haven't developed the instincts they should expect. After spending some time coding (not literally), you are acutely aware of the logical granularity that can be expected in the standard library and packages. A good example is what happened to my girlfriend. My girlfriend is a computer science major. I recently gave her some feedback about the code she wrote for her C + + homework. As part of the task, she needs to check whether the given input is a letter. This is roughly her implementation: char
<span style="color:var(--highlight-color)"><code><span style="color:var(--highlight-keyword)">bool</span> <span style="color:var(--highlight-literal)">isLetter</span>(<span style="color:var(--highlight-keyword)">const</span> <span style="color:var(--highlight-keyword)">char</span> someChar) { <span style="color:var(--highlight-keyword)">const</span> <span style="color:var(--highlight-keyword)">char</span> letters [] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; <span style="color:var(--highlight-keyword)">int</span> i = <span style="color:var(--highlight-namespace)">0</span>; <span style="color:var(--highlight-keyword)">for</span> (i = <span style="color:var(--highlight-namespace)">0</span>; i < <span style="color:var(--highlight-namespace)">26</span>; ++i) { <span style="color:var(--highlight-keyword)">if</span> (letters[i] === someChar) { <span style="color:var(--highlight-keyword)">return</span> <span style="color:var(--highlight-literal)">true</span>; } } <span style="color:var(--highlight-keyword)">return</span> <span style="color:var(--highlight-literal)">false</span>; } </code></span>
I can't even be angry that this is such a direct and logical implementation. But as an experienced developer, instinct tells you that there must be a solution to this common problem. In fact, my immediate instinct is to use the numerical nature of the c character and just check whether the input character is within a specific ASCII a-z range. But because of my "programming intuition", I suspect that this is still too much work for such a common problem. I told her Google if there was a way to check whether the character was a letter. It turns out that there are - I may have used it a hundred times, but I don't remember (things happen in enough programming - you don't remember everything). isalpha
Lesson:
Developing an intuition about whether something already exists to solve your problem is critical to success as a developer. For better or worse, the only way to reinforce this intuition is to write more code in a different set of languages and paradigms.
Find the most direct way to solve the problem
Beginners are absolutely amazing at finding direct or literal solutions to problems. Please give the example in the previous section. Although my girlfriend's solution is definitely not the most effective (memory or calculation wise), it is very clean and complete the work. While a solution that favors the most brute force may be costly, most of the time it doesn't really matter. I'm not saying you should leave performance out of the tuyere and rely only on brute force solutions, but here's a lesson to learn. To understand this lesson, it is important to look at a key difference between beginners and experts: what they are trying to achieve. isLetter
Beginners usually program to learn how to program, while experienced developers usually program as a means to achieve their goals (work, personal projects, etc.). In universities, computer science teachers rarely give assignments with performance requirements. This leads students to learn programming without considering performance. At first, it looks like a huge deficit in the way we teach people programming, but in fact I think it's one of the few things higher education does right. Worrying about performance is often a symptom of a very deadly disease called "premature optimization". According to my observation, once programmers start worrying about performance, it's hard for them to stop.
If it doesn't take extra time to optimize performance, it won't be a problem, but it does. Plus performance is almost irrelevant (this comes from low-level C/x86 programmers), it's obvious why optimizing performance all the time can be problematic. This effect is so powerful that I have actually seen that beginners arrive at a working solution faster than a very capable and experienced developer, just because beginners only care about making it work, and senior developers think it must perform well. Ironically, even if seniors complete the "performer" solution, you can't distinguish them from beginners.
Lesson:
Direct and naive solutions are often enough, also known as Occam's razor
/**I assume that many readers will disagree with this section because it may look like I encourage people to write bad code. This is definitely not the goal. If so, I think we should spend more time making the code readable and maintainable and less time optimizing performance*/
Everything is a must
One of the most frustrating behaviors I've always seen among beginners is the inability to code outside the class paradigm. I blame every university for only introducing students to Java computer science programs. I'm generally a language agnostic, but I always find that the requirement that "everything must be a class" is a particularly stupid aspect of Java. We understand that someone in the sun really likes OOP. The price of this obsession is that 20 years later, Java is still trying to reorganize itself into the language that people really want to use.
To understand why this is a real problem, not just a complaint about Java, we must consider how beginners learn. Beginners tend to internalize concepts because of the granularity they present. Therefore, when they use languages such as Java that enforce the class paradigm, they tend to understand that the smallest unit of code is the class and expect each variable to be like an object. Using another object-oriented language, such as C + +, will soon prove that the smallest unit of code is not necessarily a class - writing an entire class for primitive and stateless logic is often too skilled and cumbersome. Don't get me wrong. In many cases in real life, these semantics and guarantees are desirable - in fact, that's why Java is one of the most common languages.
The problem is that these semantics paint a misleading picture for first-time programmers. As you narrow down the Java world, you will notice that few programming languages are object-oriented. This is why it is so dangerous to learn programming in only one language. You won't learn programming in the end. You will learn a language in the end.
When I discuss this belief with other developers, they often argue that these shortcomings are actually beneficial because Java avoids shooting beginners in their feet. Although this may be a good reason for Fortune 100 companies to hire a large number of untrusted programmers, we should not introduce programming to such beginners.
Lesson:
The ability to generalize is one of your most important tools as a developer. It's perfectly understandable to start learning a single language, but it's absolutely important that you branch and diversify your understanding quickly. Doing so will take you from a pretty good < insert language here - programmer to a great developer.
Powerful syntax and structure
Beginners tend to rely more on consistent syntax and structure than experienced developers. Considering that humans are pattern driven, code syntax and semantics are one of the most obvious examples of patterns, which actually makes sense. For experienced developers, understanding the role of a function (and how it works) is usually a problem of defining it and reading the source code. It seems inconceivable not to work this way, but I can almost guarantee that even the best developers have not started like this. Pattern matching is so important to human learning that it goes far beyond programming. For example, English is known as one of the most difficult languages to learn. When some English learners are asked why English learning is so difficult compared with other languages, they usually point out the inconsistency of English learning and the lack of reliable language rules.
/**FTR: for beginners, one of the best arguments about Java is its powerful and consistent semantics*/
Recently, when I helped beginners write a C + + course with relatively complex internal state, a practical programming example occurred. Depending on the target / expected use of the class, it makes sense to change the state of some methods directly while others return copies. After writing quite a few methods together, I left the beginner to write the rest by myself. Later, when I came back, my students didn't make much progress and told them they were trapped. Specifically, they are confused about which methods are changing state and which methods are not mutated. Developers only need to see if they are experiencing a logical mutation, because they only need to see if they are experiencing a logical mutation. But beginners are not fluent enough to quickly parse the same logic (even if they have written it before), but rely on the syntax and structure of the code. After reviewing the code, I realized that their struggle was partly my fault.
When we implemented the initial approach together, I was convinced that there was a good combination of variable and immutable. What I don't realize is that these methods bring a misleading model to beginners. Specifically, each variable method is a void function, and each invariant method has a return type.
<span style="color:var(--highlight-color)"><code><span style="color:var(--highlight-keyword)">class</span> <span style="color:var(--highlight-literal)">MyType</span> { <span style="color:var(--highlight-keyword)">void</span> <span style="color:var(--highlight-literal)">addElem</span>(<span style="color:var(--highlight-keyword)">int</span> elem); MyType <span style="color:var(--highlight-literal)">createCopy</span>(); ... };</code></span>
I inadvertently taught my students a model that was clearly incorrect in practice. So the moment they need to change or change, things collapse. I said it was my fault because at the end of the day, I was lazy. As an experienced developer, I don't rely on syntax and structure as much as the actual logic. This is stupid because the code I write can be improved, leaving zero ambiguity for beginners and removing potential errors at almost zero cost to me. How did I do it? By using the const keyword, this allows me to clarify whether a method has the potential to mutate state: bool removeElem(int elem) void printElems()
<span style="color:var(--highlight-color)"><code><span style="color:var(--highlight-keyword)">class</span> <span style="color:var(--highlight-literal)">MyType</span> { <span style="color:var(--highlight-keyword)">void</span> <span style="color:var(--highlight-literal)">addElem</span>(<span style="color:var(--highlight-keyword)">int</span> elem); <span style="color:var(--highlight-keyword)">bool</span> <span style="color:var(--highlight-literal)">removeElem</span>(<span style="color:var(--highlight-keyword)">int</span> elem); <span style="color:var(--highlight-keyword)">void</span> <span style="color:var(--highlight-literal)">printElems</span>() <span style="color:var(--highlight-keyword)">const</span>; MyType <span style="color:var(--highlight-literal)">createCopy</span>() <span style="color:var(--highlight-keyword)">const</span>; };</code></span>
Lesson:
It's easy to forget how others will interpret the code you write. Consistency and the use of language features enable you to write digestible and unambiguous code.
conclusion
Many initial patterns that you embrace and adopt as a beginner will fall on the roadside as you are introduced to more efficient and maintainable solutions. Having said that, I hope this article shows that sometimes even the most experienced developers can benefit from a little "no learning". While beginners may not be comfortable with advanced features and language traits, sometimes it's a gift when work needs to be done.