📜 ⬆️ ⬇️

“Programmer pragmatist. The journey from the apprentice to the master ": briefly about the main thing (part two)

I continue to outline the book “ Programmer-pragmatist ” - a desk collection of recommendations for all occasions. The first part of the summary can be found here .
This material covers the second half of the text: these chapters deal with minimization of binding, principles of testing, order of work on the project, documentation and interaction within the team.



Chapter 5: Flexibility vs. Fragility


To keep up with today's dizzying pace of development of the industry, it is necessary to write flexible programs with a minimum of connections. Otherwise, the program will quickly become obsolete or become so unstable that it will be impossible to eliminate errors, and ultimately will be at the tail of a crazy race into the future.

Tip 36: Minimize Linking Between Modules
')
Split your program into cells (modules) and limit the interaction between them. If one module is at risk and needs to be replaced, then the other modules should be able to continue working.

It is necessary to keep track of how many modules you interact with at each operation and, most importantly, how and for what reason. Systems that have a large number of unnecessary dependencies are, in most cases, highly unstable and resource intensive in terms of maintenance. In order to keep the number of dependencies to a minimum, follow Demeter's law when designing methods and functions.

Using Demeter's law makes your program more adaptable and sustainable, but you have to pay for it. You will have to create a large number of wrapper methods that simply send the request further to the delegate. These entails costs of runtime and disk space, which can be very significant, and for some applications, even beyond. As with any technique, you must weigh the pros and cons for a particular application and build trade-offs.


Tip 37: Customize, not Integrate

Use metadata to configure the application: fitting parameters, global user parameters, etc. Metadata is any data that describes the application - how it is performed, what resources are required to use, etc. Usually, data is accessed and used at stage run, not compile.

Tip 38: Place the abstractions in the text of the program, and the details - in the field of metadata

Metadata can be used not only for preferences, but also more widely. Our goal is to think descriptively (denoting what should be done, not how it should be done) and create highly dynamic and adaptable programs. This can be done by adhering to the general rule: to program for the general case and to place all the specifics in another place - beyond the limits of the compiled program kernel.

This approach has several advantages:

  1. It forces you to minimize communication, which leads to a more flexible and adaptable program.
  2. It forces you to create a more sustainable, abstract construction by bringing all the details out of the program.
  3. You can customize the application without recompiling. You can also use this level of settings to provide workarounds for serious errors.
  4. Metadata can be expressed in a language that is better adapted to the subject area than universal programming languages.
  5. Finally, you can implement several different projects using the same application core, but with different metadata.

As mentioned in the section “The Benefit of Plain Text”, it is recommended to submit customization metadata in plain text format - this makes life easier.

Tip 39: Analyze the sequence of operations to increase concurrency

Time is a category that is often ignored in software architecture. There are two temporal aspects of importance to us: parallelism (events occurring at the same time) and ordering (relative position of events in time). When constructing a sequence of operations, it is necessary to achieve maximum parallelism by determining those processes that can be carried out simultaneously.

Tip 40: Design using services

To manage time aspects instead of components, we recommend creating services — independent, parallel objects, hidden behind well-defined, consistent interfaces.

Tip 41: When designing, there is always room for concurrency.

When working with a linear program, it is easy to make assumptions that ultimately lead to carelessly written programs. But parallelism makes one think about what is happening a little deeper. Since many events can now occur simultaneously, you may suddenly encounter dependencies related to the time factor. First of all, you need to protect any global or static variables from parallel access, and at the same time ask yourself the question, why do we need one or another global variable. In addition, you must make sure that you provide consistent information about the state, regardless of the order of calls, without relying on the confluence of circumstances.

Tip 42: Separate Visual Views from Models

By weakening the connectivity between the model and its visual representation / controller, you gain more flexibility for almost nothing. This technique is one of the most important ways to ensure reversibility.

Model An abstract data model that represents a target object. The model does not have direct information about any visual representations or controllers.

Visual presentation . A way to interpret the model. It subscribes to changes in the model and logical events coming from the controller.

Controller . A way to control the visual presentation and supply the model with new data. It publishes events for the model and visual presentation.

Obviously, we do not want to have three separate copies of the same data. Therefore, we create a model - the data itself and the usual operations for their processing. We can then create individual visual representations that display data in various ways: as a spreadsheet, graph, or amount field with accumulation. Each of these visual representations may have its own controllers. None of these tools has any effect on the data, only on the presentation.

This is the key principle on which the model-visual representation-controller paradigm is based: the separation of the model from the graphical interface that represents it, and the controls for visual presentation.

Tip 43: Use bulletin boards to coordinate workflows.

Initially, bulletin boards were developed in artificial intelligence systems for solving large-scale and complex tasks - speech recognition, knowledge-based decision making, etc. A bulletin board is a space where consumers and producers of information can exchange data anonymously and asynchronously. In programming, with the help of such systems, it is possible to save active objects (and not only data) on a bulletin board and retrieve them if the fields are partially matched (through patterns and stencil characters) or using subtypes. Since we can store objects, we can use a bulletin board to design algorithms based on the flow of objects, not just data.

The great advantage of this programming style is that it removes the need for mixing heterogeneous interfaces, allowing you to create a more elegant and consistent system.

Chapter 6: While You Write the Program


Writing programs is not a mechanical procedure. Every minute we have to make decisions that require careful deliberation and decide a lot in the life of the program. Disciplined developers constantly monitor the situation, look for potential problems and find themselves in the right position if an unforeseen happens.

Tip 44: Do not write a program based on coincidence

Often we write code relying on luck, and we take random successes as a guarantee for the correct operation of the system. This usually happens in the following ways:

Random implementation : everything “seems to be working”, but in fact it should not - in reality, the subroutine is not designed for what you do with it. Success is due to an undocumented error or boundary conditions.

Random context : the program will work, but only in a specific context that you consider to be unshakable, although it is not (for example: the program has a graphical interface; the user speaks the same language as you, or is well versed in programming) .

Implicit assumptions : you attract a lack of factual base, take matches for patterns and make incorrect logical connections.

To avoid this, it is enough to accept a few rules:

  1. Always be aware of what you are doing.
  2. Do not write programs blindly - work only with what you understand well.
  3. Act on the plan
  4. Rely on verified facts.
  5. Check and document all assumptions that you are not sure of.
  6. Properly prioritize, move from complex to simple
  7. Do not let past experience overshadow the current situation.

Tip 45: Evaluate the order of your algorithms.

A special type of assessment that pragmatists use almost daily is an assessment of the resources used by the algorithms (time, processor performance, memory size). Often this type of assessment is crucial.

Most non-trivial algorithms handle some variable input arrays. Typically, the amount of input data has an effect on the algorithm: the larger it is, the longer the execution time of the algorithm and the amount of memory used. In this case, the most important algorithms are not linear: their execution time or memory requirements increase much faster than the number of lines in the code.

When writing any programs that contain loops or recursive calls, you should be in the habit of evaluating the requirements for execution time and memory size. It is not necessary to turn this into a formal procedure, rather quickly checking whether we act rationally in the given circumstances. However, sometimes there are situations when it is necessary to carry out a more detailed analysis. In this case, the “O ()” notation (“O-large”) is a very useful way — a mathematical way of designating approximations.


Tip 46: Check Your Grades

Speaking of theory, do not forget about practical considerations. When working with small arrays of input data it may seem that the execution time increases linearly. But if the program processes millions of records, then suddenly the execution time increases dramatically as the system begins to "slip." Pragmatists try not to rest on the bare theory, but also to test the system in conditions close to the standard ones. Approximate estimates are good, but in reality the speed of your program in real conditions of operation and with real data matters.

When choosing a suitable algorithm, it is also necessary to adhere to a pragmatic approach - the fastest algorithms are not necessarily the best for a particular case. In addition, it is necessary to fear premature optimization. Before you spend your valuable time improving the algorithm, it always makes sense to make sure that it really is a bottleneck.

Tip 47: Reorganization should be done frequently and as early as possible.

As the program develops, it becomes necessary to rethink earlier decisions and revise individual fragments of the program text. This process is completely natural. The rewriting, processing and re-planning of the program text is described by the general term “reorganization”.

The program can be considered as requiring reorganization in the following cases:


Often, reorganization is postponed, citing a tight time frame. But this excuse should not become the norm: if you cannot reorganize now, then later, when you have to take into account the greater number of dependencies, it will take much more time to fix the problem.

Martin Fowler offers a couple of simple tips on how to reorganize, so that it doesn’t do more harm than good:

  1. Do not attempt to reorganize and add functionality at the same time.
  2. Before starting reorganization, make sure that the test is successful. Test as often as possible. In this case, you will immediately see the violation that was caused by the changes made.

Tip 48: Design for Testing

Like our colleagues working with hardware, we need to build test tools into programs from the very beginning and carefully test each piece before attempting to put everything together.

When you design a module or an entire program, you must design a contract and a program to verify its implementation. Moreover, if we design according to the contract, then we test “against the contract” - that is, we check the boundaries in order to make sure that the program does not go beyond them. This allows us to take into account the boundary conditions and other aspects that would otherwise be ignored. It is best not to eliminate the errors, but to avoid them from the very beginning.

Unit tests should not be somewhere on the periphery of the source tree. They should be located so that it is convenient to work with them. If the project is small, you can embed a unit test in the module itself. For larger projects, you can put each of the testing procedures in a separate subdirectory. In any case, you must remember that if the test is difficult to find, then no one will use it.

By making the test procedure available, you give developers who can use your program two invaluable resources:

  1. Examples of how to use all the functionality of your module.
  2. Tools for constructing regression testing procedures to verify the correctness of any changes that will be made to the program afterwards

Tip 49: Test your programs, otherwise your users will.

All the programs you create will be tested - if not by you and your team, then by the end users, so you can just as well do it yourself. A little forethought will greatly assist in minimizing maintenance costs and reduce the number of calls to technical support.

Tip 50: Do not use the wizard function program if you do not understand everything in it.

No one can deny - creating applications is becoming more and more difficult. Developers try to be in shape, but do not always find time to master new tools to perfection. In this case, a magic wand was invented - a master function.

The problem with it is that the use of the wizard function, designed by some computer guru, does not automatically come from the developer of a computer expert. This principle, of course, works for any technology, but here the situation is even more serious: the wizard functions generate code that becomes an integral part of the application and thereby pushes you to programming for the sake of coincidence. If you use the wizard function and do not understand everything that it does, you will not be able to maintain your own application in working order and will spend a lot of effort on debugging.

Chapter 7: Before Starting a Project


Have you ever had the feeling that your project is doomed even before it starts? Perhaps this will be the case if you do not first accept some basic rules.


Tip 51: Do Not Collect Requirements - Seek It Out

The word "collection" suggests that all the requirements are already available, just grab. This is not entirely true. Requirements rarely lie on the surface. Usually they are deep under a layer of assumptions, misconceptions and policies.

In its most general form, the requirement formulates the need for any operations. Ideally, this should be a clear, unambiguous statement based on a real, concrete need. In reality, users do not always clearly understand and clearly express what they need. Before considering any request as a requirement, it is necessary to establish whether the need really exists and whether it is correctly formulated. Ultimately, the developed program will have to solve problems, and not just meet the stated requirements.

Tip 52: Work with the user to think in user categories

There is a simple method: to look from the inside at the requirements of your users, you need to become a user yourself. Try it yourself to do the actual typical tasks for which the program is designed, or watch how someone else does it. So you get an idea of ​​what impression the system produces in business, and in addition, build a more trusting relationship with your audience.

Tip 53: Abstractions live longer than the details.

When drafting documents containing requirements, there is a serious risk of excessive specification. Good documents remain abstract. In terms of requirements, the simplest formulation that accurately reflects the essence of the need is the best. However, this does not mean that you can admit uncertainty: you need to fix the underlying semantic invariants as requirements and document the concrete or current practice as a policy.

Tip 54: Use the project glossary

It is very difficult to create a successful project in which users and developers mean the same thing with different words or, even worse, talk about different things using one term. Create and keep up to date the “project glossary”, where all specific terms will be defined. All project participants, from end users to help desk professionals, are required to use the glossary to avoid misunderstandings.

Tip 55: Do not think "outside" - better find these limits.

Absolute restrictions must be observed, no matter how unpleasant and ridiculous they may seem. On the other hand, some supposedly obvious limitations in reality may not have any basis for themselves - then you can ignore them. In many difficult situations, the solution is to determine which constraints really exist, and which we only see.

Faced with a serious problem, imagine all the possible directions in which you can move. Do not reject any options, no matter how useless or stupid they may seem. Now review the entire list and explain why you cannot follow a particular path, providing evidence for each item.

Sometimes you have to work on a problem that turns out to be much more difficult than you thought. At this point, you need to take a step back and ask yourself a few questions:


In many cases, there is a solution as soon as you try to answer one of these questions. Often, a new interpretation of requirements may take with them a whole bunch of problems.

Tip 56: Listen to doubts - start when fully prepared

On the one hand, you need to listen to the inner voice that tells you that the time has not come yet. On the other hand, one can fall into the trap of fear of a clean sheet and endless procrastination. A good way to get out of this situation is to start creating a prototype product. Choose an aspect with which you think you will have problems and try to implement it in draft form. Then there are two possible scenarios for the development of events: either you get bored, you realize that your fears were untenable, and switch to work, or you really find the problem area and think about how to act, in advance, thereby saving time for the whole team.

Tip 57: Some things are better done than describing.

Writing a specification is a big responsibility. The problem is that many designers find it difficult to stop. They believe that, until each minor detail is painted to the smallest detail, they freely eat their own bread. This is wrong for several reasons. First, it is naive to believe that the specification is generally capable of fixing every detail of a certain system or its requirements. Secondly, the natural language itself, which we use for communication, is not adapted for such a degree of accuracy. In the end, even a two-page treatise signed by the customer will not save you from requests and criticism when the project is submitted. Finally, there is a “straitjacket effect” - an unnecessarily detailed description narrows your room for maneuver and leaves no room for creativity.

A pragmatist considers the collection of requirements, design and implementation as different stages of one process - the delivery of a quality system to the customer. Each of these aspects should smoothly pass into another without artificial borders. So think about how these or other language will affect your work in the future.

Tip 58: Do not be a slave to formal methods.

Formal methods have a number of serious drawbacks. Most of them record the requirements using a combination of diagrams and explanatory phrases that the general public understands only with the support of the developer’s explanations. Consequently, verification of requirements by the actual user under such a scheme, in fact, is absent.

Formal methods also encourage over-specialization without proper group interaction. This often leads to poor communication, wasted efforts and a deaf war between designers and programmers. We prefer to perceive the system on which we work as something holistic. It is unlikely that a deep insight into the essence of every aspect of the system will work out, but you must know how the components interact with each other, where the data are placed and what the requirements are.

Finally, most modern formal methods combine a model of a static object or data with some kind of mechanism for constructing an event diagram or process. We have not yet met the mechanism that was displaying the desired degree of dynamism. This pushes developers to set static relationships between objects, which in fact should be dynamically interconnected.

This does not mean that formal methods cannot be used - just consider them as one tool from your arsenal. Pragmatists look at methodologies with a critical eye, then take the best from each and incorporate them into a set of practical technologies that are constantly being improved.

Tip 59: Expensive tools don't always create better solutions.

Do not feed the false authority of the method. Try not to think about how much this or that tool costs, looking at the results of its work.

Chapter 8: Pragmatic Projects


Tip 60: Organize a team based on functionality, not job responsibilities.

The traditional organization of the team is based on an outdated method of creating software known as the “waterfall method”. Individual team members are assigned roles based on their job responsibilities. Some teams dictate a strict division of responsibility: those who make programs are not allowed to communicate with those who test them, and they, in turn, are not allowed to communicate with the main architect, etc. Some organizations further complicate the task, forcing different subgroups report through separate management chains. However, the opinion that different actions when working on a certain project — analysis, design, program writing and testing — can occur in isolation from each other, is erroneous.

Divide your employees into small groups, each of which will be responsible for a specific functional aspect of the final version of the system. Each group has jointly established commitments to other groups participating in the project. This set of commitments changes with each new project, as does the distribution of people into groups.

In this way, you contribute to benign isolation: it is not that the teams work "blindly" without taking into account each other's actions, but that major changes can be closed within one group without affecting the others (see the principle of orthogonality). With proper execution, this approach can significantly reduce the number of crossings in work, reduce time costs, improve quality and reduce the number of bugs, and also make teams more cohesive.

However, this approach only works with responsible developers and strong leadership. Creating a pool of autonomous groups and letting them loose in the absence of leadership is the shortest path to disaster. The project needs at least two managers - one technical, the other administrative.

Tip 61: Do not use manual procedures

Any recurring task arising during the project should be performed automatically. , ̆ . ̆ ̆ ̆ , .

62: ̆ . ̆ . ̆

, , - . . , ( ) ̆ , ( ) -̆. , ̆ ̆ . , .

, , . , , . .



63: ̆, ̆

, , , , «». ̆ , , .

, :


̆ : . .

64: ̆

, , . , . , , , ̆ , .

65: ̆ ̆,

, ? , , , . : , , . , ̆ . , . ̆, . ̆ , , , , , .

66:

, ̆ ̆ ̆ . , , . .

, ̆ ̆ , . , , . , , .

67: ̆ ̆

, . ̆ — ̆ ̆ . , ̆ , .

68: ̆ , ̆

, , , . . , , . , , , . , , – .

, - : , , .

̆ ̆ ( ), , , ̆. . , ̆ , .

, :

  1. ̆, ̆ ̆. , . ̆ , .
  2. ̆. . , ̆ .
  3. ̆, ̆. .
  4. ̆. ̆, .

69:

, . , (, ). ̆ , , . .

̆ , . . ̆ ̆, ̆ , . , - , , , . : , , , -, ̆ -.

70: ̆

, - . , , ̆ ̆ , , . ̆ , . , — , .

, , , - . .

Source: https://habr.com/ru/post/348932/


All Articles