How workflow development affects the decomposition of tasks
One of the most important factors affecting the speed of development and the success of the project launch is the correct decomposition of the product manager idea into tasks for programming directly. How to do it right? Take the script of the new feature from the product and immediately start coding? First, write the acceptance tests, and then code that will provide their passing? And, maybe, shift everything to the shoulders of developers - and let them decide during the spam poker themselves?
Let's think about it and identify the problems that can arise in the process of separation of tasks, and the ways to solve them. In this post, we will discuss the basic principles of decomposition of tasks when working in a team. My name is Ilya Ageyev, I'm the head of QA in Badoo. Today I'll tell you how workflow affects decomposition, how different are testing and laying out tasks that arise as a result of decomposition, and what rules should be followed to ensure that the development process goes smoothly for all participants.
Why is this important?It should be remembered that the development process is not just a session of code writing. When we talk about development, I urge you to look at the whole process, starting from setting the task and ending with the stable work of the feature from our users. If you do not take into account all the steps that precede encoding and follow it, then it's very easy to get into a situation where everyone is doing something, perform their KPI , receive bonuses, and the result is deplorable. Business is bent, competitors are "strangling," but at the same time all are fine fellows.
Why is this happening? It's simple: human psychology makes people look at the situation from the point of view of own comfort . The developer does not always want to think about what will happen to the code after it is written. Has solved a problem - and it is good. It is very rarely interested in this (that is why we, IT specialists, and work in this industry - our motivation is mainly based on the interestingness of the tasks), because there is so much uncertainty in relations with people. Much more comfortable, many developers feel themselves sitting at the computer and focusing on solving their own interesting task - the blockers with neural networks - they do not want to be distracted and think about some product managers, deadlines, users who will then use their ingenious product (or else they will start criticizing!).
This is not bad and not good - we value developers for a thoughtful and competent solution of technical problems. But a narrow view of problems often stops development. And it's about the development of not only specific people, but the company as a whole. After all, the growth of the company and the improvement of corporate culture are possible only with the growth of each employee. Therefore, it is important for us sometimes to get out of the "cocoon" and force ourselves to look at the problems more broadly, in order to stimulate this growth.
And, of course, if such an important stage as decomposition is entrusted to a person who looks at everything solely from the point of view of their own convenience, there is a real risk of a heap of problems at subsequent stages: when the results of his work merge with the results of others, with code review, when testing, laying out in production, and so on.
Thus, determining for yourself how to properly break this or that task, figuring out where to start and where to finally come, it is important to consider as many factors as possible, and not look at the problem only "from your bell tower." Sometimes, in order for everything to work faster and more efficiently in the next stages, you have to do something more difficult and slower at the stage for which you are responsible.
A good example is the writing of unit tests. Why should I waste my valuable time writing tests if we have testers who will test everything afterwards? And then, that unit tests are needed not only to facilitate the coding process - they are also needed at subsequent stages. And they are needed as air: with them the process of integration and regression testing is accelerated in tens, hundreds of times, they are based on the automation pyramid . And this is even if you do not take into account the acceleration of your own work: after all, having "touched" the code in some place, you yourself need to make sure that you have not broken anything inadvertently. And one of the fastest ways to do this is to drive out unit tests.
WorkflowMany teams, in order to somehow formalize the relations between the participants in the process, agree on the rules of work in the team: they agree on the coding standards, the overall workflow in the version control system, sets the release schedule, and so on.
Needless to say, if you initially agree on the process, not taking into account the entire life cycle of the feature, you can get a slowdown and "rake" in the future? Especially if we take into account the growth of the project and the company. About premature optimization is not forgotten, but if there is a process that works well on different scales, then why not use it initially?
Speaking of workflow development, many who use Git immediately remember (in vain) about some kind of "standard git-flow", considering it to be ideal, correct, and often implement it at home. Even at conferences where I spoke, talking about workflow in Badoo, I was asked several times: "Why did you invent your own, why do not you use standard git-flow?" Let's understand.
Firstly, usually when talking about this flow, we mean this picture. I took it from the article Vincent Driessen "A successful Git branching model", which describes the scheme , quite successfully worked on several of his projects (it was in the distant 2010).
Today, some large players in the code hosting market generally offer their flow by criticizing "standard git-flow " and describing its shortcomings; give their schemes, recommendations, techniques.
If you search on git-scm.com (it's good to google at all), then with surprise you can find out, that no recommended (and even more so "standard") workflow does not exist. All because Git is, in fact, a framework for the storage of code versions, and how you organize this very storage and joint work, depends only on yourself. You should always remember that if a certain flow "took off" on some projects, this does not mean that it will also suit you completely.
Secondly, even in our company different teams have different flows. Floating the development of server code in PHP, daemons in C / C ++ and Go, the flow of mobile teams - they are different. And we came to this not at once: we tried different variants before we stopped on something concrete. By the way, not only the workflow, but also the testing methodology, setting tasks, releases and the delivery principle differ in these commands: what is delivered to your personal servers and computers (smartphones) of end users can not be developed identically by definition.
Third, even accepted workflow is more a recommendation than an uncontested rule. The tasks of the business are different, and it's good if you managed to choose a process that covers 95% of the cases. If your current task does not fit into the selected flow, it makes sense to look at the situation from a pragmatic point of view: if the rules prevent you from doing efficiently, to hell with such rules! But be sure to check with your manager before making a final decision - otherwise the mess may start. You can trivial not take into account some important points that are known to your leader. And, probably, everything will go like clockwork - and you will be able to change the existing rules so that this will lead to progress and will become the key to growth for all .
If everything is so complicated, moreover, flow is not a dogma, it's just a recommendation, then why not use one branch for everything: Master for Git or Trunk for SVN? Why complicate?
For those who look at the problem one-sidedly, this one-branch approach may seem very convenient. What for to be excruciated with any branches, to be soared with stabilization of the code in them if it is possible to write the code, zakommitit (bully) in the general storehouse - and to be pleased lives? And it's true, if not a lot of people work in the team, it can be convenient, as it eliminates the need to merge branches and organize branches for release. However, this approach has one very significant drawback: code in a shared store can be unstable. Vasya, working on task number 1, can easily break the code of other tasks in the shared store, filling his changes; and until it corrects them / does not roll them off, the code can not be laid out even if all the other tasks are ready and working.
Of course, you can use tags in the version control system and code-frieze , but it's obvious that the approach with the tags is small differs from the approach with branches, at least because it complicates the initially simple scheme. And the code-frieze especially does not add speed to work, forcing all participants to stop the development before stabilizing and laying out the release.
So the first rule of good decomposition of tasks sounds like this: tasks must be broken so that they fall into the shared storehouse in the form of logically completed pieces that work by themselves and do not break the logic around them.
Feature branchesWith the whole variety of workflow options in our company, they have a common feature - they are all based on individual branches for features . This model allows us to work independently at different stages, develop different features without interfering with each other. And we can test them and merge them into the common store, only making sure that they work and do not break anything.
But this approach also has its drawbacks, based on the very nature of the fiche. In the end, after isolation, the result of your work will need to be merged into a common place for everyone. At this stage, you can solve a lot of problems, ranging from merge conflicts to a very long testing / bug-fixing. After separating into your code branch, you isolate not only the shared storage from your changes, but also your code - from the changes of other developers. As a result, when the time comes to merge your task into a common code, even if it is checked and works, "dances with a tambourine" begin, because Vasya and Petya in their branches touched the same lines of code in the same files - the conflict .
Modern storage systems versions of the code have a bunch of handy tools, merger strategies and more. But to avoid conflicts all the same it will not be possible. And the more changes, the more ornate they are, the more difficult and longer it is to resolve these conflicts.
Even more dangerous are the conflicts associated with the logic of the code when SCM merges the code without problems (because there are no conflicts in the files), but because of the isolation of the development some common methods and functions in the code have changed their behavior or even been removed from the code. In compiled languages, the problem, as it may seem, is less acute - the compiler validates the code. But the situation, when the method signatures did not change, but the logic changed, nobody canceled. Such problems are difficult to detect, and they further distract the happy release and cause the code to be re-tested many times after each merge. And when there are a lot of developers, a lot of code, lots of files and lots of conflicts, everything turns into a real hell, because while we were fixing the code and rechecking it, the main version of the code has already gone far ahead, and we have to repeat everything anew. Do you still not believe in unit tests? Hehe-heh!
To avoid this, many people try to merge the results of common work into their branch as often as possible. But even compliance with this rule, if the fiche is large enough, will not help to avoid problems, no matter how hard we try. Because you get other people's changes to your code, but your changes are nobody does not see. Accordingly, it is necessary not only to pour someone else's code more often into your branch, but also your code in the shared store - too.
Hence the second rule of good decomposition: fiche should contain as few changes as possible to get to the common code as quickly as possible.
Parallel operationOk, but how then can I work in separate branches if several programmers are working on the same task, broken up into parts? Or if they need changes to common parts of the code for different tasks? Both Petya and Vasya use a common method, which, within the framework of the Petite task, should work according to one scenario, and in Vasya's task, differently. How should they be?
Here a lot depends on your release cycle, because the moment of completion of the task we consider the moment of its getting into production. After all, only this moment guarantees us that the code is stable and working. If you did not have to roll back the changes from production, of course.
If the cycle of releases is fast (several times a day you are laid out on your servers), then it is possible to make the features dependent on each other on the stages of readiness. In the example with Petya and Vasya we do not create two tasks above, but three. Accordingly, the first one sounds like "we change the general method so that it works in two variants" (or we start a new method for Petit), and two other tasks are the tasks of Vasya and Petit, who can start work after the completion of the first task without crossing and not interfering with each other.
If the release cycle does not allow you to publish often, then the example described above will be an unreasonably expensive pleasure, because then Vasya and Pete will have to wait days and weeks (and in some development cycles and a year) until they can start working on their tasks.
In this case, you can use an intermediate branch common to several developers, but not yet stable enough to be laid out for production (Master or Trunk). In our flow for mobile applications such a branch is called Dev, at scheme Vincent Driessen it's called develop.
It is important to keep in mind that any change in the code, even the fusion of branches, the infusion of common branches into a stable Master, etc., must necessarily be tested (remember about the conflicts by code and logic, yes?). Therefore, if you came to the conclusion that you need a common code branch, then you need to be ready for another stage of testing - after the moment of the merge, you need to test how the feature integrates with the other code, even if it has already been tested in a separate branch.
Here you can notice that you can only test once - after the merger. Why test before it, in a separate branch? True, you can. But, if the task in the branch does not work or breaks the logic, this inoperative code will fall into the general storehouse and not only that will prevent colleagues from working on their tasks, breaking some sections of the product, so also it can lay the bomb if, on the wrong changes, who will decide to base a new logic. And when such tasks are dozens, it is very difficult to look for the source of the problem and repair the bugs.
It is also important to understand that even if we use an intermediate development branch of code that may not be the most stable, the tasks or their pieces in it should be more or less complete. After all, we need at some point to be unveiled. And if in this branch the feature code breaks each other, then we will not be able to do it - our product will not work. Accordingly, having tested integration of features, it is necessary to correct as soon as possible bugs. Otherwise, we will get a situation similar to the one when one branch is used for everyone.
Therefore, we have the third rule of good decomposition: tasks should be divided so that they can be developed and released in parallel.
Feature flagsBut what about the situation when a new change in business logic is big? Only programming this task can take several days (weeks, months). Do not let us dump the unfinished chunks of features into a common storehouse?
And here we are! In this there is nothing to worry about. The approach that can be applied in this situation is feature flags . It is based on the introduction in the code of "switches" (or "flags") that enable / disable the behavior of a particular feature. By the way, the approach does not depend on your branching model and can be used in any of the possible ones.
A simple and understandable analogue can be, for example, an item in the menu for a new page in the application. While a new page is being developed in pieces, the item is not added to the menu. But as soon as we finished it and put it together, we add the menu item. It's the same with phcheflag: we turn the new logic into a flag inclusion condition and change the behavior of the code depending on it.
The last task in the process of developing a new big feature in this case will be the task of "including the phchef flag" (or "add a menu item" in the example with a new page).
The only thing to keep in mind when using phishing flags is to increase the test time for the feature. After all, the product needs to be tested two times: with the included phaeflag included. You can save money here, but you should act extremely delicately: for example, to test only the state of the flag that is laid out to the user. Then in the development (and piecemeal) tasks, it will not be tested at all, but will be tested only while checking the last task "to include the phcheflag". But here it is necessary to be prepared for the fact that the integration of feature pieces after the flag is enabled can go through with problems: bugs in the early stages can be detected, and in this case finding the source of the problem and eliminating errors can be expensive.
ConclusionSo, when decomposing tasks, it's important to remember three simple rules:
Tasks should be in the form of logically completed pieces of code.
These pieces of code should be small and should be as fast as possible in the common code.
These pieces should be developed in parallel and laid out independently of each other.
Where is it easier? By the way, an independent calculation, in my opinion, is the most important criterion. From it one way or another, the remaining items flow.
I wish good luck in developing new features!
|Vote for this post
Bring it to the Main Page