Show me a solution because of which the developers will not argue, and I will put you a beer



There are two irreconcilable camps. Some say: you must accurately and competently describe your commits. Each commit is a complete, meaningful piece of work. If you cannot make a clear and simple description of a commit, then you have the wrong commits.

Others consider that you make commits as you like, this is part of your personal workflow. But the pool of requests is described in detail: what is done, how is done, why is it done. As tested, what problem solves, what you should pay attention to.

I am a staunch supporter of the second approach - it is inconvenient for me to beat my work into micro-pieces. I take a small task and randomly rush around the code base, experimenting and making changes in the order in which it turns out. If I could click on a button, and my work would be restructured into good commits, I would click on it. But there is no button, but I don’t want to break myself. At the same time, I reached a certain skill in describing the pool of requests. I happened to figure out the code in Microsoft (through outstaff, it doesn’t count), and there I got the top standards for processing a pool of requests. Over the years, I have just developed this practice. Usually I managed to convince the team to use just such an approach.

But at the last job, I got devlid - a staunch supporter of detailed commits. Oh, we argued for a long time. My subordinate position played a role, the Sovkovsky habit of agreeing with the main thing is not easy to shut up. I was not so categorical as usual, and was laid on both blades. Stacked, but not convinced.



If suddenly the developers unite in a single government, establish rules and begin to proclaim certain approaches outlawed, a civil war will begin the very next day. All because in the world of developers there is a belief in the objective and measurable. In the truth independent of human perception. And if all this exists, it means that one of the disputants is clearly objectively wrong.

My first serious working dispute occurred quite early. I was still a green june, who thinks he is apper middle, and indeed a very cool and smart developer. One real developer from my team threw PR. We had a practice that all team members make a review code. I opened the code, and almost immediately saw the problem. A man wrote a test for some function that took a number. So he fed her 0, 1000, and random (0, 1000).

At that time, I very keenly felt that other teammates saw me as a stupid newcomer. And he waited a moment to smear them with his vision. I was lucky - random in tests!

I did not really understand the theory of unit testing, but I read a couple of books, and firmly remembered - the same unit test on the same code base should give the same result. I spent about an hour thinking through the comment so that it did not look toxic, but it made it clear that only the monkey, which was taught to count just yesterday, could come up with such a decision. In the end, of course, he looked extremely stupid, like everything that yesterday's interns who imagined themselves to be developers squeezing out of themselves.

The next day, I realized that I had opened a portal to hell. There was a hundred more under my comment. For the rest of the day, instead of a team of six developers, we were a team of six monkeys. We argued in English in PR itself, although everyone was Russian. When the stock of words was not enough, they called up. They threw themselves at each other with links, quotes from books, studies, turned to the individual. A colleague answered one of my comments with a screenshot from an English dictionary in order to ridicule my English. But we have not come to a compromise.

The question was complicated. On the one hand, we all did not know at all what this parameter was responsible for, our function passed it to the other, from an internal network to which there were no docks. Therefore, theoretically, the function could fall on any value, which means the test could catch it, and then we could anticipate the error on the prod. On the other hand, we already had the problem of randomly wallowing builds. Because of this, if the project was building locally and was crashing in, we stupidly ordered a new build and did not look at any logs. After all, the build took four hours, and nobody would look at the pull request until it passed CI.

In addition, the cars were often busy - it was much more pragmatic to order a build while it was still being ordered than to look for a potential “problem” (which almost always turned out to be a flying timeout in the integration test). This meant that even if the random test fell out, we would not pay attention.

Everything was resolved on the next dealik. We phoned the whole team, and for half an hour continued to argue - in Russian. We did not notice how our American development manager joined the call, and continued to smash each other with arguments at elevated tones. Until they heard “Hey guys, it is absolutely not important. Just merge it as is. " I don’t know how long he listened to us before, and as he understood what we were talking about, but we stopped abruptly arguing. They froze and forgot about it. Parted in peace, but not convinced.



Since then, I have gained experience in order to understand - but do not give a shit at all, random, handicap, or boundary cases. This damn unit test will interfere - we will rewrite. Everything is better than arguing all day. But if you imagine that I am in an ideal world, where it is always necessary to make only more correct decisions, I don’t know what to do. We froze a test with randomness, and now I understand this as a choice in favor of reliability, but to the detriment of the convenience of development.

The other day I faced a similar dilemma. I played with the new timecode and wrote a thing that can be used like this

// LessThan<T>  MoreThan<T> -    ,   . 
// ,        

//      , 
//   ,   100
const consumeNumberLessThan100 = (v: LessThan<100>) => 
    doWork(v);

//     ,   100
const consumeNumberMoreThan100 = (v: MoreThan<100>) => doWork(v);

const sample = (x: number) => {

    //     check  , 
    //   x  100
    //  "<"  ">"  , 
    //     -   
    const lessThan100 = check(x,'<', 100);

    if (lessThan100) {
        //     -  
        //       
        //    ,  lessThan100  - LessThan<100>
        //    
        consumeNumberLessThan100(lessThan100); 

        //   -  ,   , 
        //  ,   > 100.
        consumeNumberMoreThan100(lessThan100);
    }

    const moreThan100 = check(x, '>', 100);

    if (moreThan100) {
        consumeNumberMoreThan100(moreThan100); // 
        consumeNumberLessThan100(moreThan100); // 
    }

    consumeNumberLessThan100(x); // 
    consumeNumberMoreThan100(x); // 
}

I thought, damn it, this is very cool - so you can catch a large number of errors at an earlier stage. The design of my value restriction tools is terrible, but for now it's just a concept. In the future, you can easily expand it, build a powerful DSL and formulate really complex conditions for parameter matching guaranteed at the compilation stage. I figured out how it can increase the reliability of any code base, perked up, and sent out a snippet to various familiar developers.

Opinions were divided again, and not in my direction. Re-complication, over-engineering, is unsupported, everyone will jam your guard with the help of a cast to eni. Not readable. Good as an experiment is bad in the present project. Examples of use are drawn from the finger. Get down to business, Phil.

Proponents of the approach said, yes, so reliable. The sooner you catch a mistake, the better. In addition, if you write a function that works with a limited number, you still have to write a check, but it will only work in runtime, and will increase the amount of code in the body of the function.

Now I am a little smarter than before, and learned to hear the arguments. I imagined myself writing a function protected by my type in a real project. Like everyone who uses it is asking me what the hell this is. How, after fumbling the chip, they begin to coat the code base with a custom, rotten-looking DSL. As we check three hundred times the values ​​that in fact will never exceed the permissible. The approach is really terrible to use, some problems can be solved or smoothed out, but for example, such a case

consumeNumberLessThan100(90);

Do not smooth in any way. I’ll have to prove to the compiler that 90 is less than 100. I will supply any activity, and it will turn out

consumeNumberLessThan100(assert(90, '<', 100)); 

It doesn't look very cool. All the arguments against are on hand, but they do not contradict the arguments for. It turns out a dilemma - ease of development, or reliability. Here we fall into the trap, we begin to think that we need to calculate what kind of convenience is there and what kind of reliability is there. But convenience and reliability in development are very, very complex things. They consist of thousands of parameters.

For example, convenience is when the IDE compiles the code for you on a pair of entered characters, when the code is easy to read, when you can change the functionality of the method without looking at the document. When the compiler is not loaded with static analysis, the build is fast, the text editor instantly renders characters. But when the compiler detects and highlights the error for you, this is also a convenience. And also it is reliability. Which in turn is assembled from a huge number of completely different things.

You have to sit down and calculate how long the project will last, in which direction it will go, how many times in the history of mankind someone will call one or another method of your code base, which developers will work here. This is an endless list of questions for the good half of which it is impossible to calculate the correct answer. Just guess.



Once, a friend asked me to prepare technical questions for an interview with Andrei Breslav. I went to the dock of Kotlin to find controversial points in the design. They are there, as elsewhere, darkness. But what interested me most was the approach to handling exceptions. An exception handler in Kotlin is an expression, a completely functional and reliable approach. That's just to handle errors is not necessary. Which drops all reliability to zero. And this is interesting because right in the dock, the developers were not too lazy to explain their choice: "there are studies that mandatory error handling greatly reduces the productivity of developers with a slight decrease in errors."

I'm crazy, how can you write code if you have no idea what to do when it doesn’t work correctly !? If you don’t know how to handle the exception, then do not handle it - not a solution to the problem, but shelving. At some point, the code will fall off, and a problem that always exists will cause damage that could have been anticipated.

But a logical argument against is not needed, you can do just statistics. For kotlin developers, research breaks the logic because they have a philosophy. Their philosophy is pragmatism. Iron, unbreakable pragmatism, consistently built into all the features of this programming language. The idealists who saw the Haskels / Idris and the govnokoders who write the golang do exactly the same. The philosophy of reasonable compromise reigns in the F # code base. And there is no feeling that one of them is right, and the rest are fools.

All these people are much smarter and more experienced than people like me, and they seem to have long understood that you are developing a philosophy for yourself, and then you simply follow it. Because the killer feature of any philosophy is to resolve the dilemmas.

And so philosophy appeared in everything in IT, and philosophy is the exact opposite of the idea of ​​a single and true objectivity, because philosophy offers to choose subjective truth for yourself.

Each of us has our own philosophy - it is not described in one word, it is a complex set of patterns for making decisions. And we often change or expand them. In practice, it turns out that you have a project with your own philosophy, which is written in a programming language with your own philosophy, using frameworks and tools, each of which has its own philosophy. And developers write this project, each with its own philosophy. And every time you need to make some kind of decision, only a combination of circumstances affects which one will be made. Not complexity management, not experience, not approach, not knowledge, but just an accident.

And all these projects work. The lion's share of all the tasks that developers solve is the elimination of the consequences of errors of other developers, but projects work and solve people's problems. Smart people come up with practices and architectural patterns. Programming languages ​​with powerful type control and contracts - everything, if only the developers were less mistaken. And every day I open the Edge Board at work, and I see there are more bugs than tasks. Where is every bug, this is another, managing complexity, crap developer.

The development philosophy is simply the choice of the way you get your hands on the final. But if there is no philosophy, but only pure objective logic, then any argument will come down to the problem of the omnipotent creator and the unbearable stone.



I’ve been in development for a long time, and I’ve learned how to do what they say, even when I fundamentally disagree, and began to write super-detailed commits. My devlid is a real vampire. It requires not just a description - he wants me to write what each file does, why it does it, how it does it. What problem does commit commit?

I added eight extra-detailed commits, the same detailed description of the request pool - and I liked it kapets. Since then, we almost did not work on this particular project, but from time to time I go there to admire these commits. Seriously, really, really cool. And in all other projects, except for my personal ones, I now apply this approach.

It was very difficult for me to rebuild the workflow. A month and a half has passed, and it's still hard for me to write code like this. But it seems worth it.

Or not. I honestly don’t know. And I don’t know that if it’s definitely worth it, does it make me two months old a complete idiot. About ten people walk around somewhere in the world whom I have learned to not make such commits. Are they idiots? I do not think so.



My podcast

All Articles