Several things are guaranteed to increase over time: the distances between the stars, the entropy of the universe, and the business requirements for the software. Many articles write "Do not complicate!", But do not write why or how to do it. Here are 10 clear examples.
1. Engineers know best
We, engineers, consider ourselves the smartest people. Well, since we create different things. And this error often leads to over-engineering. If you have planned and built 100 modules - Business will always ask you for the 101st, which you have never thought of. If you gather your strength and solve 1000 problems, they will come to you and put 10,000 new ones on the table. You think that you have everything under control, but in fact you have no idea what direction the road will take you tomorrow.

')
During my 15 years of work as a programmer, I have never seen Business give out complete and stable software requirements once and for all. They always change, expand. And this is the nature of the business, not the mistakes of the people who manage it.
Moral : Casino (business) always wins.
2. Reuse of business functionality
When Business gives us more and more demands (as expected), we sometimes say to ourselves: “Ok, let's try to group and summarize everything that is possible!”.

That is why most MVC systems end in Big Model or Bold Controller. But, as we have already found out, business requirements will never stop being added, which means this is a road to nowhere. In fact, we need to respond to this like this:
Example : Once for one of our customers, we developed a user profile module. We started with a classic CRUD controller, well, because this is just a user profile and everything should be simple there. This all ended with the implementation of 13 different scenarios for creating and filling out this profile: registration via social network, regular, simplified, fast, followed by editing, which looks completely different for subsites, etc. They had very little in common. Similarly, the “view order” and “order edit” scenarios are different, for example.
Try to divide business functionality vertically first before dividing it horizontally. It even makes it easier to divide a monolith into microservices. Sometimes it becomes a monolith for you or services at all. Otherwise, it becomes more and more difficult to change parts of your system.
Moral : Prefer isolated actions, avoid combining.
Tip : Take some visible to the user part of your program (form \ page \ command). How many context switches does a programmer need to understand all the code associated with it?
3. Let's summarize everything
(Somewhat echoes the previous point, but sometimes it happens independently of it)
- Need to connect to the database? Let's write a Generic Adapter.
- Make a request? Generalized Request.
- Pass parameter to it? Generalized Parameter.
- Collect a few parameters into something? Generalized Builder.
- Reflect the result obtained in something? Generalized Data Mapper.
- Handle user request? Generalized Request.
- Do something? Generalized Artist.
- etc.
Sometimes engineers enter. Instead of solving business problems, they spend time choosing the right parent class. And the answer is simple.

Software architecture always plays catch-up with business requirements. So even if you find an ideal abstraction today with the help of some kind of magic, its expiration date will be complete with it - see item number 1 above - the business always wins. So the best characteristic of the build quality of your architecture is how quickly it can be disassembled. There is a
great article on how to write code that will be easy to remove, and not easy to change.
Moral : Code duplication is better than incorrect abstraction.
Moreover, duplication of code sometimes helps to find the right abstraction. Because when you see several parts of the system using the same code in the same way, you can understand what unites them. The quality of Abstraction is determined by the quality of its weakest link. Duplication of the code allows you to look at different situations from different angles and see the boundaries of the Abstraction more clearly.
4. Writing wrappers for all external libraries
There is a practice of writing wrappers (wrappers) for each external library used (mainly due to following the style of the main product, sometimes due to the reasons described in the previous two paragraphs). A very popular approach to enterprise programming. Programmers in dynamic languages ​​like Node / Ruby / Python simply add the library and start using it. But Enterprise Developer cannot do this - it needs to create a wrapper. And then another wrapper over the wrapper. Perhaps it made some sense in the 2000s, when most libraries were a jumble, and indeed there wasn’t much open source.
But today is
the 2016th year . External libraries improved by an order of magnitude. They are just fantastic. Most likely they are written by excellent programmers and tested better than your main code. They have a distinct API. They can be embedded logging. You don’t have to waste your time writing wrappers around good code. In addition, most wrappers are completely meaningless. Their interface is very closely related to the underlying library, often simply as a reflection of one-to-one functions. If the library changes in the future, most of the code using this wrapper will also have to be changed. Creating an “agnostic” wrapper that can remain unchanged even, for example, with a complete replacement of the library being wrapped with another is not a trivial task. Such tasks are sometimes set with the thought of the potential "configurability" of a general solution, the very idea of ​​which will be discussed a little lower.
Moral : wrappers should be exceptions, not the norm.
5. Blind application of code quality metrics
Thoughtlessly following the concepts of code quality (like the fact that all variables must be declared as "private final", each class must have an interface) will not make your code AUTOMATICALLY good. Look, if you have not yet seen, on the Enterprise version of
FizzBuzz or
Hello World . Prorva code. At the micro level, each class follows the principles of SOLID and uses different patterns. If you set a code quality analysis tool on this code, it will say that this is a great product, just a pretty sight. But if you take a step back, you will immediately see what a nightmare this program is, just printing "Fizz Buzz".
Moral : always take a step back and look at the big picture.
Similarly, automated tools define test code coverage well, but they say absolutely nothing about whether you are testing what you need or not. They can measure performance in some test, but they won't say anything how well your program processes the data otherwise. They all look at the micro level: what does this class \ method \ string do. And only Man looks at the big picture.
Learn a different programming language and try other approaches to solve problems you already know. This will make you a substantially better programmer. Do not dwell on one thing.
5.1. Sandwich Layers
Let's take some closely related functionality and divide it into 10-20 layers, where each layer has no meaning without the rest. Well, because we want to implement the concept of "Testing Code", or the "Principle of Unified Responsibility", or call it something else fashionably. In the past, this was done through inheritance. Class A is inherited from B, which is inherited from C, etc.

Today, people do the same thing, except that each class is now represented by an interface / class pair and injected into the next layer, because we have the same SOLID.
Moral : Concepts require thoughtful application. They can not be used simply anywhere and always as tools.
6. Syndrome Novelties
We studied generics. Now we have a simple “HelloWorldPrinter” to become “HelloWorldPrinter <String, Writer>”.You do not need to use generics, when the actually required form will always be instantiated with the same data types. Parameters are sufficient in most cases.
Studied the Strategy pattern. Now all “if” statements will be a strategy .
Why?
Learned to write DSL. Now we have DSL everywhere and for everything.Well, I don't even know ...
Found moki. Now we will have everything locked up and down.Oh, how ...
Metaprogramming is a smart thing, let's use it here and here!Explain why ...
Extension methods \ concepts \ something else is a good thing, let's use the circle!And let's not be!
Moral : nothing needs to be applied everywhere and always. And the “Morality” section is also not necessary to write in each paragraph.
7. “X – st”
- Configurability
- Security
- Scalability
- Maintainability
- Extensibility
- ...
Vague. Unchanged. Hard to argue.
Example 1 : Let's create a CMS so that the client can add fields to this form by himself.
Result : Clients will never use it. When they are needed, they will find a developer and they will be puzzled. Perhaps, instead of a full-fledged CMS, it was worth writing a short instruction on how to quickly add a field.
Example 2 : Let's design a large layer for working with databases to simplify Configuration. We should be able to change the database by editing a single config file.
Result : For 10 years of work, I saw only one project, where it was necessary to change one database to another for objective reasons. And, when it came to this, then “editing one config file” didn’t work at all. There was a mass of related work. Incompatibility, gaps in the functionality. And once again a client asked to transfer HALF of our data models to a new NoSQL database. We had a “magic file” with a database connection, but firstly, only relational, and secondly for all data, not for half. Perhaps all these dances with configurability make sense when it comes to something like the Oracle database, which costs like hiring 20 programmers - it’s really convenient to be able to switch to something else if necessary. But for modern databases, all we need is a simple set of vertical DAO classes instead of a wide horizontal ORM layer. There is no single model that successfully combines SQL and NoSQL, so perhaps we should really separate them, using one or the other in each case, instead of trying to combine the incompatible and frighten the wrong incorrect abstractions.
Example 3 : We built an OAuth authorization system for enterprise clients. For the administrators of this system, we were asked to add another level - authorization of administrators through Google OAuth. "Because it must be safe." If someone hacks our OAuth authentication, it is necessary that at least the accounts of administrators are not available. Google OAuth is a reliable thing, so there seems to be nothing special to object to.
Result : Anyone who wants to hack this system does not need to break through the OAuth layer. You can find a vulnerability in something simpler (there are always those). As a result, all the costs of supporting the two levels of OAuth (and they passed through the entire system) yielded roughly no results. It was better to spend this time eliminating common vulnerabilities.
Moral : Do not take all these characteristics with the end of the "-st" as a constant given. They have their price - so clearly define the Scenario \ History \ Usage.
8. Cycling
It is always cool at the beginning. But in a few years it will already be Heritage Cargo.
A couple of examples:
- Own libraries (HTTP, mini-ORM, caching, configuration)
- Its frameworks (CMS, event handling, multithreading, background tasks)
- Own tools (build system, deployment system)
What's wrong with that:
- The seemingly trivial tasks actually require decent knowledge and deep immersion in the subject area. Some kind of “process launcher” requires an understanding of the management of processes in the OS, the operation of daemons, the input / output system and a whole heap. CMS is not just something that inserts data into templates — there are dependencies, checks, wizards, generalizations, etc. The most simple-looking library can have non-trivial functionality.
- All this needs to be maintained and updated.
- If you put the code in open access - everyone will not care. Well, maybe, except for those who worked on this code before.
- People who understand this code will leave sooner or later. And besides them, no one in the world understands this implementation.
- The introduction of existing libraries and frameworks into the project, sharpening them for your needs takes time right now. But the invention of own bicycles requires much more time in the long term.
Moral : Reuse. Borrow good. Review your decisions.
If you still decide to build your bike - at least make it according to the principle of "internal open source" in your company. Explain to people why. Show the first sketch. Offer them to use or improve this. And think 10 times, whether to continue to build this bike, even if your colleagues do not express their approval.
9. Develop your code
As soon as you have implemented something in a certain way - all the others begin to use it in the form in which it is implemented. No one wonders if this is right. As long as your code works, this is “good code.” People are beginning to use it even for tasks that he was not supposed to solve. Sometimes it turns out good, and sometimes very bad. And here you need not to be afraid to take and change your code, improving it for current tasks. Healthy software systems always live, change. Unhealthy - only supplemented. If a piece of code has not seen commits for a very long time - something is wrong with it, it “smacks”. Every piece of code needs to evolve. Here is a
great article describing this.
Here's how teams work on tasks, but how they should do it, Every Day:
Moral : refactoring is part of any development. No code remains the same.
10. Incorrect timing estimates
Often we see how good teams seem to be or individual coders suddenly produce a frankly weak product. We look at the code base and we are surprised - “How is it possible, was it really created by this team / person, but I thought they were \ clever / clever!”. Quality requires not only skill, but also time. Even smart developers often overestimate their capabilities. And suddenly they find themselves in front of a burning deadline, painfully inscribing a terrible crutch into the code, giving a chance to somehow meet the deadlines.
Moral : misjudging deadlines harms the quality of your project even before the first line of code is written.