Inability to talk about code
“To talk about the code” means to understand the order of execution of instructions (“to execute the program in the head”), knowing what the purpose of the code is.
Symptoms
- The presence of "magic", "voodoo" code or code that has no relation to the goals of the program, but is still carefully supported (for example, initialization of variables that are never used, calling functions that are not related to the goal, creating output data which are not used, etc.).
- Repeated calls to idempotent functions (for example, calling save () several times, “for sure”).
- Correction of errors by writing redundant code, which replaces the data obtained during the execution of a faulty code.
- "Yo-yo code", which converts values into different representations, and then converts them back to exactly the same representation from which they started (for example, converting a decimal number to a string, and then back to a decimal number, or padding the line and then trim ohm).
- “Bulldozer Code”, which creates the impression of refactoring by breaking pieces of code into procedures that, it is true, cannot be used elsewhere (high cohesion).
Treatment
To cope with this flaw, the programmer should use the debugger built into the IDE as an aid in the event of an inability to “step along” the code line by line. In Visual Studio, for example, you can set a breakpoint at the beginning of the “problem” zone and then step by pressing F11, while simultaneously watching the values of variables, until it becomes clear what exactly the code does. If there is no such possibility in the target environment, one should practice in that in which it is possible.
The goal is to reach a level where you no longer need a debugger to execute code “in your head”, when you are patient enough to think about how the code changes the state of a program. As a reward, you will be able to determine redundant and unnecessary code, as well as the ability to find errors in existing code without re-implementing the entire algorithm.
')
Poor understanding of the programming language model
Object-oriented programming is an example of a programming language model, as well as functional, and declarative programming. All of them are significantly different from procedural or imperative programming, just as procedural programming is significantly different from assembler or programming using GOTO instructions. There are also languages that follow some common programming model (say, object-oriented programming), but make some improvements in it, such as generator expressions (list comprehensions), generic programming, duck typing (duck typing) ) etc.
Symptoms
- Use any necessary syntax to “break out” of the model proposed by the language, and write the rest of the program in an imperative / procedural style.
- (OOP) Attempts to call non-static functions or assignment of variables in non-instantiated classes, problems understanding why such constructs are not compiled.
- (OOP) The presence of a huge number of ".... Manager" classes that contain all the methods of manipulating objects, which, in turn, do not contain (or contain little) methods.
- (Relational) Treat the database as a repository of objects, execute all JOINs and verify the integrity on the client side.
- (Functional) Creating many versions of the same algorithm for processing different types or operators instead of transferring higher-order functions to the generalized algorithm.
- (Functional) Manual caching of the results of deterministic functions on platforms that do this automatically (SQL or Haskell).
- (Functional with “pure” (pure) functions) Use copy-paste from someone else's code to overcome I / O and monads.
- (Declarative) Setting individual values in imperative code instead of using data binding.
Treatment
If your fault is the result of ineffective learning, then your best teacher is the compiler. There is no more effective way to learn a new programming model than to start a new project by deciding to use new constructions, no matter what degree of awareness. You also need the practice of explaining the capabilities of the new model through terms that you are already familiar with, and recursively building your new vocabulary until you understand all the subtleties. For example:
Step 1: "OOP is just a structure with methods."
Step 2: "Methods in OOP are simply functions that execute a mini-program with its own set of global variables."
Step 3: "Global variables are called fields, some of which are private and invisible outside mini-programs."
Step 4: "The very idea of having private and public elements is to hide implementation details and expose a clean interface, and this is called encapsulation."
Step 5: "Encapsulation means that my business logic should not be clogged with implementation details."
The last step is the same for all languages, since all languages are trying to lead the programmer to the possibility of expressing the meaning of the program without burying this meaning in the implementation details. Take functional programming:
Step 1: "Functional programming is when everything is done through chains of deterministic functions."
Step 2: “When the functions are deterministic, they do not need to be computed until it is required in the output data, and only the part that is really required is to be computed. This is called lazy and partial computing. "
Step 3: “In order to support lazy and partial calculations, the compiler requires me to write functions in terms of the transformation of one parameter, sometimes making such transformations another function. This is called currying. ”
Step 4: “When all functions are curried, this allows the compiler to choose the best execution plan with the help of the constraint solver.”
Step 5: “By allowing constraint solver to solve all the unimportant details myself, I can write programs describing what I want, and not exactly how to get it.”
Lack of research skills / Chronically poor knowledge of the capabilities of the platform
Modern languages and frameworks now come with a surprisingly large number of built-in commands and features. Some technologies (Java, .Net, Cocoa) are perhaps too great to expect from any programmer, even very good, that he will learn them faster than in several years. But good programmers will look for built-in functions that do what they need before they start writing their own. And excellent programmers will be able to split their tasks into parts, identify abstract problems, and then find the frameworks, patterns, models and languages that can be applied.
Symptoms
The following symptoms do indicate a problem only if they continue to manifest themselves in the programmer’s work for quite a long time after he began to master a new platform.
- Inventing or creating mechanisms that are already built into the language, for example, events and handlers, regular expressions.
- The invention of classes and functions that are already in the framework (for example, timers, collections, sorting and searching algorithms). *
- Messages "Write me a code, pliz!" In various forums.
- "Round code", which performs its task with a much larger number of instructions than is required (for example, rounding a number through a conversion to a string and back).
- Constant use of old-fashioned techniques, even if the new ones are better in the current situation (for example, creating full-fledged named functions where you need a lambda expression “at a time”).
- Unreal size "comfort zone" when a person is ready to go extremely long way, solving the problem of primitive means.
* - random duplication also happens, usually proportional to the size of the framework, so to estimate, proceed from the number.
Treatment
The programmer can not acquire such knowledge without stopping, and, most likely, it was a desperate attempt to make each function work, no matter what it cost. He needs a technical reference book on the platform and the ability to look at it with minimal effort, which means that he must have either a hard copy on the table next to the keyboard, or a second monitor for the browser. To get the initial habit of writing “correctly”, he should try to refactor his old code, aiming to reduce the number of instructions by at least an order of magnitude.
Inability to understand pointers
If you do not understand pointers, then there remains a very small range of program types that you can write, since this understanding allows you to create complex data structures and reasonable APIs. Managed languages use links instead of pointers that are similar, but provide automatic dereferencing and prohibit pointer arithmetic to eliminate a whole class of errors. They are still quite similar, but the misunderstanding of the concept of pointers will be reflected in poor data design and errors, the reasons for which lie in the difference between passing by value and by reference.
Symptoms
- Inability to write a linked list, or write code that inserts / deletes nodes from a linked list without losing data.
- Selection of arbitrary large arrays for data of variable size instead of linked lists or other dynamic data structures.
- The inability to find or correct errors associated with pointer arithmetic.
- Changing dereferenced values from pointers passed to a function, without understanding that it will change the values outside the scope of the function.
- Creating a copy of the pointer, changing the dereferenced value through a copy with the expectation that the original pointer still points to the old value.
- Serialization of the pointer, while the dereferenced value must be serialized.
- Sorting an array of pointers by comparing the pointers themselves, not the values.
Treatment
My friend Joe lived in the same hotel as me, but I did not know in which particular room. However, I knew where his friend Frank stayed. So I went there, knocked on the door and asked Frank: “Where did Joe stay?”. Frank did not know, but he knew in which room Theodore, Joe’s colleague, lived. So I went to Theodore's room and asked where Joe was staying, and Theodore told me that Joe was in room 414. That's where I found him!
Pointers can be described using various metaphors, data structures can be found many analogues. The above is a simple analogy for linked lists. Misunderstanding of pointers does not happen because of how exactly pointers are described (you cannot describe them in more detail than they have been described so far). Misunderstanding happens when a programmer tries to imagine what is happening in computer memory, and this picture merges with his understanding of ordinary variables, which are very similar. Help in reasoning about what is actually happening can this “translation” of the algorithm into a simple story. This is worth doing until finally the programmer has the ability to distinguish and represent pointers and data structures as easily as scalar values and arrays.
Recursion issues
The idea of recursion is fairly easy to understand, but programmers often have problems in presenting the result of a recursive operation or how complex calculations can be performed with a simple function. Problems with the “where you are” representation, when you start writing the condition for the continuation of recursion, or with the formalization of the parameters of the recursive function can make the development of a recursive function very difficult for you.
Symptoms
- Terribly complex iterative algorithms for solving problems that can be solved recursively (for example, bypassing the file system tree).
- Recursive functions that check the same condition before and after a recursive call.
- Recursive functions that do not check the basic condition.
- Recursive procedures that concatenate / accumulate values in a global variable or transfer an output variable and do not implement tail recursion.
- The confusion about what to pass as parameters for a recursive call, or recursive calls that pass constant parameters.
Treatment
Roll up your sleeves and be prepared for stack overflows. Begin by writing a function with one basic condition and one recursive call that uses the same, unmodified argument as it was received at the input. Stop, even if it seems to you that this is not enough, and run. Look at the stack overflow, go back and add a change to the argument when recursively calling. Overflow again? Excessive withdrawal? Then a few more iterations, switching between changes in the condition and in the call, until you start to sense how the function transforms the input data. Resist the desire to use more than one basic condition or more than one call if you do not know why you are doing this.
Your goal is to have the confidence to begin, even if you do not have the full sense of “where you are” in an imaginary recursive path. Then, when you need to write a function in a real project, you will start with a unit test and continue writing using similar techniques.
Signs of an ordinary programmer
Inability to think in collections
The transition from imperative programming to functional and declarative will require you to be able to think about operations on data sets. Collections will become your primitive instead of scalar values. This is required if you use SQL and relational databases, if you develop programs that need to be scaled proportionally to the number of processor cores, or when you write code that will run on SIMD chips (such as modern graphics cards and game consoles) .
Symptoms
These symptoms matter only if they manifest themselves in working with a platform with declarative or functional capabilities that the programmer should be aware of.
- Execution of atomic operations on elements of a collection inside a for or foreach loop.
- Map or reduce functions that contain loops to iterate over a set of data.
- Getting a large set of data from the server and calculating amounts on the client, instead of using aggregate functions in the query.
- Writing business logic functions with tragic side effects, like changing user interfaces or performing I / O functions.
- Entity classes that open their own database connections or get file descriptors and keep them active throughout the life of each of the objects.
Treatment
Imagine a casino dealer who shuffles a deck of cards and shuffles the two parts together by scrolling, this can push you to think about collections and how you can operate with them. Other stimulating visualizations:
- entrance to the toll road, equipped with several points of payment (parallel processing);
- streams that are connected into streams that connect into sleeves from which a river is obtained (the parallel execution of reduce / aggregation functions);
- zipper (simple join'y);
- transported RNA that collects amino acids and connects with messenger RNA inside the ribosome to become a protein (multi-stage funtion-driven join's);
- the same thing happening in the billions of cells in the orange tree to turn the soil, water and sunlight into orange juice (map / reduce in large distributed clusters);
If you are writing a program that works with collections, think about all the additional data and records that your functions need in order to work on each of the elements. Use map functions to connect them in pairs, before you apply reduce to each of these pairs.
Lack of critical thinking
If you are not critical of your own ideas and do not look for flaws in the course of your thoughts, you will definitely miss problems that can be solved before you start coding. If you are also not able to critically evaluate your code, you can only learn very slowly, by trial and error. This is the root of both lazy thinking and egocentric thinking, so the symptoms can come from two completely different sides.
Symptoms
- "Business rules engines".
- Thick classes with static utilities, or libraries that solve many different tasks, but having the same namespace.
- Architectures that began to need epicycles.
- Adding columns to tables for those not directly related to this data table.
- Non-observance of uniformity in the naming of variables and methods.
- The mentality of the “peasant with a sledge hammer”, or changing the conditions of the problem so that it can be solved using one specific technology.
- Programs that by their complexity overshadow the task that they have to solve.
- Pathological and unnecessary "defensive" programming (defensive programming), "enterprayzny code."
Treatment
Start with Paul and Elder's Critical Thinking, work on managing your ego, and practice resisting the desire to defend yourself when you trust your ideas to friends and colleagues to criticize.
When you get used to what other people are learning, testing your ideas, start to fully explore your ideas on your own. In addition, you need to develop a sense of proportion (to understand how design fits the size of the task), the habit of testing assumptions (so that you do not overestimate the size of the task), and a healthy attitude to failure (Isaac Newton was wrong about gravity, but we still love him , and his assumptions are very important to us).
Well, finally, you must be disciplined. Awareness of the flaws of your plan will not make you more productive until you train the will to correct and restructure what you are working on.
Pinball programming
If you kick the machine in exactly the right way, pull the spring back as far as necessary and hit the buttons on the paws in the correct sequence, only then the program will run smoothly.
Symptoms
- One try-catch block that includes the entire body of Main (), restarting the program.
- Using strings or integers for values that might have a more appropriate type in a strongly typed language.
- Packing complex data into separate lines and parsing them in each function where this data is used.
- Inability to use assertions or method contracts on functions that make assumptions about their arguments.
- Use sleep () to wait for another thread to complete its work.
- Switch instructions over non-enumerable values without the "otherwise" block.
- Use automethods or reflections to call methods based on unprepared user input.
- Using global variables to return more than one value from a function.
- Classes with one method and a set of fields that need to set values, in fact, to pass parameters to the method.
- Update related data in the database without the use of transactions.
- Attempts to restore database state without transactions and rollback.
Treatment
Imagine that your program is water. It can leak through each crack and fill any cavity, so you need to think about the consequences that it can flow anywhere else, except for the places you built for it.
You need to familiarize yourself with the mechanisms of your platform that allow you to make your program reliable and plastic. Three main types of such mechanisms:
- Those that stop the execution of the program before any damage is caused by the fact that something unexpected has happened, and then they help you determine what went wrong (type systems, assertions, exceptions).
- Those that direct the program along the path that best handles surprises (try-catch blocks, multimethods (multiple dispatch), event-oriented programming).
- Those that suspend thread until the desired state is reached (WaitUntil instructions, mutexes and semaphores, SyncLock).
There is also a fourth mechanism - unit testing, which you use during design and development.
The use of these mechanisms should be as natural for you as the placement of commas in a sentence. To achieve this, go through the mechanisms listed in brackets and correct some old program so that it uses them wherever you can attach them, even if it turns out to be unacceptable (especially if it turns out to be unacceptable, only so you will begin to understand why) .
Ignorance of safety principles
If the following symptoms were not so dangerous, then it would be more a question of “finishing”, “polishing” for most programs. Which means that if you manifest them, then you are not necessarily a bad programmer. You are just a programmer who should not work on network programs or secure systems until you have worked on your shortcomings.
Symptoms
- Storing important information (names, credit card numbers, passwords) without any encryption.
- Storing important information using inefficient encryption tools (symmetric codes with keys embedded in the program itself; simple passwords; substitution ciphers; self-developed and unverified codes).
- Programs that do not limit their own privileges before accepting network connections or interpreting input from unreliable sources.
- Failure to perform boundary checks and other input data checks, especially in unmanaged environments.
- Creating SQL queries by concatenating strings with raw input.
- Code that attempts to prevent the exploitation of a vulnerability by trying to find the characteristics of this vulnerability.
- Credit card numbers and passwords are hashed without salt.
Treatment
Only the basic principles are listed here, but they allow you to avoid the most blatant mistakes that can compromise the entire system. For any system that processes or stores information that is valuable to you or your users, or manages a valuable resource, always request a review of your design and implementation from a security professional.
Begin auditing your own programs with code that saves input to an array or to other allocated areas of memory. Make sure that there are checks that the size of the input data does not exceed the size of the memory allocated for them. There is no other class of errors that would cause such a huge number of “holes” in security as a buffer overflow. This means that you should seriously consider managed languages and environments when writing network programs, or in any other place where security is not in the last place.
Next, check database queries that concatenate unchanged input data into an SQL query, and rewrite this code using parameterized queries, if your platform allows. Well, or add filtering / screening of all input data, if not. This is to prevent attacks from SQL injections.
After you get rid of the two most notorious classes of security-related errors, you should start thinking of all the input parameters of the program as unreliable and potentially dangerous. It is very important to determine the valid values of the input arguments in the form of a valid validating code. Your program should reject the input data, if it does not pass validation, then you will be able to repair the “holes”, correcting the validation, making it more detailed, instead of looking for features specific to these or other vulnerabilities in the input data.
You should always think about what operations your program should perform, what privileges they require. You should think about this before starting development, because this is the best way to figure out how to write a program using the minimum required number of privileges. The point of this is to limit the damage to the rest of the system, if suddenly in your program there is a vulnerability. In other words: if you no longer trust the input data, you need to learn not to trust your own programs.
One last thing: you need to learn the basics of encryption, starting with the Kirkgoffs principle. It can be formulated as “security must be keyed”. There are also some interesting conclusions based on this principle.
First, you should not trust a code or other cryptographic primitive, unless it is published publicly and has not been analyzed and tested by the community. You cannot achieve security through obscurity, through propriety or through novelty.
Implementations, even if trustworthy cryptographic primitives can have flaws, therefore avoid implementations that have not been carefully studied. All new cryptographic systems fall into the “pipeline” of research with a length of 10 years or more, and it would be good for you to use those that have already appeared on the other side of this “pipe” unharmed.Secondly, if the key is weak or stored in an inappropriate way, then it is as bad as if there was no encryption at all. If your program requires data encryption, but not decryption, or if decryption is required only occasionally, consider encrypting using only a public key from an asymmetric pair, and decrypt separately, using a private key stored in the repository with a strong password that the user must enter everytime.The more at stake, the more you have to get ready and the longer you have to design your program. This is because when your program is deployed, dozens, sometimes even millions of uninvited people will try to hack it.The rest of the security bugs usually boil down to stupid bugs, most of which can be successfully avoided by checking the input data, using resources conservatively, relying on common sense, and developing a program at a rate not exceeding the speed with which you can reason it.Signs that you shouldn't be a programmer
The following "diseases" are incurable. So if you still suffer from them after the school programming course, you better try yourself in a different profession.Inability to determine the order of execution of the program
Symptoms
a = 5
b = 10
a = b
print a
You are looking at this code and are not sure which particular number will be printed.Alternative professions
- An electrician
- Locksmith
- Architect
- Civil Engineer
Inability to think abstractly
Symptoms
- Difficulty understanding the differences between objects and classes.
- .
- .
- .
- LISP .
- -.
-
- , .
- , .
- You take for granted the presence of hidden variables and without hesitation throw any problems with the program on them!
- You think that the presence of a code in a program can change its behavior, even if it is never called.
- Your repertoire of debugging techniques includes wiping a happy golf ball, spinning your wedding ring, patting a toy dog with a swinging head. And if suddenly something is wrong, then, apparently, you either missed something or performed your rituals in the wrong order.
Alternative professions
- Frequenter of gaming machines
Indifference to the result
Programming can still be your hobby, but it is in the interest of the whole society to save the software development industry from your participation in it.Symptoms
- , «» .
- , .*
- , .
- , , , , .*
- , (, ), , , « » .
- , - .
- .
- (, « » «», , - ).
- , , .
* - most often appear under pressure from the authorities, and not at the initiative of the programmer. All the same, we will leave them here for the sake of self-examination, and note that one of them should look for a new job, and the other to return to business school and learn a few less destructive ways of making profit.Alternative professions
- Debt Recovery Officer
- Telemarketing