<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>The Hardcoded Blog</title>
 <link href="/atom.xml" rel="self"/>
 <link href=""/>
 <updated>2026-04-25T05:46:47+00:00</updated>
 <id></id>
 <author>
   <name></name>
   <email></email>
 </author>

 
 <entry>
   <title>Now working at GridGain</title>
   <link href="/work/2022/03/19/gridgain/"/>
   <updated>2022-03-19T07:02:10+00:00</updated>
   <id>/work/2022/03/19/gridgain</id>
   <content type="html">&lt;p&gt;About 2 month ago I’ve left Yandex for working at GridGain Rus.&lt;/p&gt;

&lt;p&gt;Spending almost 2 years in Yandex was an interesting experience, but I cannot name it fruitful or delighting.
Somewhere in the middle of this journey I’ve fallen into a “big tech cultural trap” that was &lt;a href=&quot;https://danluu.com/culture/&quot;&gt;perfectly preparated by Dan Luu&lt;/a&gt;.
Instead of fighting it or adapting myself, I’ve decided to leave.&lt;/p&gt;

&lt;p&gt;Now, I’m a SDET at GridGain.
I feel the best opportunities to apply my skills, my knowledge, and my passion here.&lt;/p&gt;

&lt;p&gt;Please stay tuned: this blog is not abandoned, and I plan to post more thoughts from time to time.&lt;/p&gt;

&lt;p&gt;Peace to y’all!✌️&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Lists vs. Fractals</title>
   <link href="/work/2021/11/24/list-vs-fractals/"/>
   <updated>2021-11-24T14:38:00+00:00</updated>
   <id>/work/2021/11/24/list-vs-fractals</id>
   <content type="html">&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;: I have a hypothesis that problems in programming often have fractal-like nature.
This causes delays in work and makes the management of tasks more complicated.
Our task-management tools cannot properly handle fractal tasks because they are based on the linear concept and treat task dependencies as a second-class citizen.
If we take into account the fractal nature of tasks, such management may become easier.&lt;/p&gt;

&lt;p&gt;I also propose an &lt;a href=&quot;https://github.com/ahitrin/SiebenApp&quot;&gt;example of tool&lt;/a&gt; for such kind of task management.
You may take a look at it, but it’s not obligatory.&lt;/p&gt;

&lt;h2 id=&quot;welcome-to-the-fractal-trap&quot;&gt;Welcome to the &lt;em&gt;fractal trap&lt;/em&gt;!&lt;/h2&gt;

&lt;p&gt;Let’s explore an imaginary (but quite typical) situation.
Say you have a “software project”:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a big pile of code,&lt;/li&gt;
  &lt;li&gt;and some kind of “task tracker” used to plan a work upon this code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the beginning of a new week, you choose a single “task” from this tracker and start to work on it.
This task has already been “estimated” and split into a simple, straightforward-looking checklist.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/1.png&quot; alt=&quot;An original task&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Then you simply start moving through this list.
At the first glance, these steps appear to be simple, but when you dig into the first one, it suddenly grows in size.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/2.png&quot; alt=&quot;An original task becomes a little bigger&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There might be a &lt;em&gt;blocker&lt;/em&gt; or significant subtask for almost every simple step in your software project.
Your “first subtask” grows more and more, becoming comparable in size to the whole original task.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/3.png&quot; alt=&quot;An original task has grown 2x in size&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally, you’ve found that your code to be changed is not covered with tests.
Much bad, need to fix it!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/4.png&quot; alt=&quot;We&apos;re in the fractal trap!&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Of course, this isn’t a rant against refactoring or tests!
They are truly needed, especially in complex and aged code bases.
I rant against the way we’ve used to deal with tasks.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/triangle.png&quot; alt=&quot;Sierpinski triangle&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Sierpiński triangle, image from &lt;a href=&quot;https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle&quot;&gt;Wikipedia&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Like a &lt;a href=&quot;https://en.wikipedia.org/wiki/Fractal&quot;&gt;fractal&lt;/a&gt;, a software development task could easily hide other subtasks with comparable size and/or complexity.
They could be invisible to you until you face them.
That’s why I call this “a trap”.
Fractal traps usually come from different sources: code coupling, leaky abstractions, requirements shift, and so on.
They are almost unavoidable.
I think it wouldn’t be wrong to claim that most developers have to deal with them at least sometimes.&lt;/p&gt;

&lt;p&gt;But how do we usually deal with such a kind of problem?&lt;/p&gt;

&lt;h2 id=&quot;a-classical-approach&quot;&gt;A classical approach&lt;/h2&gt;

&lt;p&gt;The most naive approach to handling this problem is simply ignorance.
Is each task growing in size?
OK, let’s deal with it.
Just keep going until the final goal is achieved.&lt;/p&gt;

&lt;p&gt;Alas, by keeping all substeps in the same task we make them quite opaque.
&lt;em&gt;Something&lt;/em&gt; is happening in the task, and it will be finished &lt;em&gt;someday&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Depending on your working environment and team agreements, this situation could or could not harm your performance.
In most cases, I’m afraid, it does.
There are thousands of words written that could explain better than me why large tasks decrease your productivity.&lt;/p&gt;

&lt;p&gt;Of course, this is a common problem, and there is a solution already!
Let’s proceed to it.&lt;/p&gt;

&lt;h2 id=&quot;divide-and-conquer&quot;&gt;Divide and conquer&lt;/h2&gt;

&lt;p&gt;Once you get tired of big tasks, you start splitting them into smaller pieces.
In your new working process, each subtask is only allowed to become a full-fledged task in your tracker once you notice that it’s big enough.
Like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/5.png&quot; alt=&quot;4 tasks instead of 1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Oh, wait!..
Much more often you’ll see it like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Ticket      Label                       Assignee
----        ----                        ----
#321        An original task            You
#322        Config migrations           You
#323        Code cleanup                You
#324        Add more tests              You
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When splitting your task into subtasks, you usually have to pay a new tax: the tax of manual dependency management.
Starting from now, your every decision about what subtask to take next gains an additional cost.
When you ask a question about relations between tasks, you have to visit one or several of them to find an answer:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Is there anything that prevents us from starting work on task #N&lt;/strong&gt;?
Well, dig into it and check whether there is any “blocker task” on the card.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Which tasks have no blockers&lt;/strong&gt;?
Visit all of them and take a look.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Which task has the highest priority&lt;/strong&gt;?
Use priorities you’ve assigned to them manually.
Of course, you should better ignore the fact that your “super-duper critical” task could easily be blocked by another task that looks like “senseless crap” in isolation.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Is it possible to reduce this tax?
Let me introduce a possible way to do it.&lt;/p&gt;

&lt;h2 id=&quot;respect-the-structure&quot;&gt;Respect the structure&lt;/h2&gt;

&lt;p&gt;Because of the task-oriented nature of most task-tracking tools, &lt;em&gt;the relations between these tasks are second-class citizens&lt;/em&gt;.
How could you ever spot the fractal structure when the only thing you get is a big unstructured pile of tickets?
To make this happen, we should try to visualize this tree and introduce our dependencies as first-class citizens.
But I believe that we deserve to have good task-management tools that could help us to handle with complex and tangled nature of software development.&lt;/p&gt;

&lt;p&gt;How could a task workflow look like in such a tool?
This vision is primarily inspired by ideas taken from a brilliant &lt;a href=&quot;https://www.manning.com/books/the-mikado-method&quot;&gt;“The Mikado Method”&lt;/a&gt; book by &lt;a href=&quot;https://twitter.com/ellnestam&quot;&gt;Ola Ellnestam&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/danielbrolund&quot;&gt;Daniel Brolund&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/mikado-book.jpg&quot; alt=&quot;Book cover&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At the beginning of a new day, you choose a single goal from this tracker and start to work on it.
Several days ago you’ve already thought about this goal and preliminarily split it into three subtasks.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/t1.png&quot; alt=&quot;an original task as a tree&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The tool highlights non-blocked tasks for you making them easier to spot.
So you simply choose to start from the first one.
To improve your focus, you &lt;em&gt;hide&lt;/em&gt; other subtasks - they are not needed at the moment.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/t2.png&quot; alt=&quot;focus on the first subtask&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At the first glance, this step appears to be simple, but when you dig into it, it grows in size.
You reflect these changes in your tracker by adding new subtasks.
In your task tracker, it’s a quick and easy operation.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/t3.png&quot; alt=&quot;new nested subtasks appear&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A count of subtasks still grow, but you’re prepared for that.
No matter how deep you dig in, your tool always allows you to control the complexity of your current working context.
You could split your tasks into parts to focus on a single chosen part.
You could draw relations between subtasks to provide an order of execution.
You could remove these relations when you see that two tasks do not depend on each other.
All these operations allow you to keep the number of your next actions small.&lt;/p&gt;

&lt;p&gt;From time to time you may switch from “working mode” into “planning mode”.
Instead of “zooming in” into a single subtask, the tool lets you “zoom out” to see a whole picture.
It may look like this, for example:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/t4.png&quot; alt=&quot;the whole view&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now let’s imagine that you decide that one of your refactorings could be performed later since it doesn’t actually block your work.
The only thing you need to do is to &lt;em&gt;insert&lt;/em&gt; a new subgoal between an original task and this refactoring.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/t5.png&quot; alt=&quot;subtask moved&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Then let’s zoom into the “Preliminary refactoring” again.
It’s become simpler than before because the “Migrate config files” subtask doesn’t block it anymore.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/t6.png&quot; alt=&quot;zoomed back to refactoring&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You may simply take the topmost subtask and solve it.
After you’ve ordered your small steps, all of them could be taken one after one.&lt;/p&gt;

&lt;h2 id=&quot;is-it-better&quot;&gt;Is it better?&lt;/h2&gt;

&lt;p&gt;This process may look scary when you’re unfamiliar with such kind of task management.
At first glance things seem worse: not only have more subtasks to manage right now, but also many more explicit dependencies between them.
The total amount of “items” has been increased compared to previous approaches.&lt;/p&gt;

&lt;p&gt;But when you look at how does it behave &lt;em&gt;in dynamics&lt;/em&gt;, things do change.
With such a tool, you could easily change the scale of thinking and planning.&lt;/p&gt;

&lt;p&gt;In the “global view” mode, you usually don’t work on any single task.
Instead, you look at relations between tasks, reorganize them, spot patterns, and think about the big picture.
It’s very similar to the “mind mapping” technique which is used to organize connections between ideas.
You only need to decide what is &lt;em&gt;relatively more&lt;/em&gt; important, and what is &lt;em&gt;relatively less&lt;/em&gt; important.
The tool takes responsibility for the &lt;em&gt;absolute&lt;/em&gt; ordering.
Every line in this graph becomes &lt;em&gt;the record of your decision&lt;/em&gt;.
And after returning to the “close view” mode, you don’t need to keep these decisions in your mind.&lt;/p&gt;

&lt;p&gt;The only thing you need to focus on is how to finish a simple “top” subtask.
When it’s done, another subtask will become unblocked.
This allows new tasks to become “top” ones.
This process strictly follows the order you’ve defined earlier.
Now your task tracker works not like mind mapping but like a TODO-list.
But it’s still the same tool!&lt;/p&gt;

&lt;h2 id=&quot;does-it-have-drawbacks&quot;&gt;Does it have drawbacks?&lt;/h2&gt;

&lt;p&gt;Of course, there are several issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tree trimming takes time&lt;/strong&gt;.
Almost obviously, when you manage many small pieces of your task and relations between them, it requires a larger part of your time.
On the flip side, detailed and flexible planning often allows one to foresee and avoid traps before falling into them.&lt;/p&gt;

&lt;p&gt;And it saves the time you might lose when escaping from these traps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It requires skill&lt;/strong&gt;.
Here’s a less obvious but more important point.
Without experience in decomposition, you often don’t know how to split your tasks properly.
But to gain experience, you need to do it regularly.
Even after ~5 years of using my tool I still find new ideas to improve my techniques.
As an example, I still don’t know how to work with structureless tasks like writing big pieces of text (especially this one).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk of context loss&lt;/strong&gt;.
Alas, this happens regularly.
Say you have two big subtasks of your current task (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt;).
Each one has several nested subgoals (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A.1&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A.2&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A.3&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B.1&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B.2&lt;/code&gt;, and so on).
While working on subtask &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; you notice that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A.2&lt;/code&gt; is a prerequisite for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B.1&lt;/code&gt; and place a dependency between them.
After some time you switch from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A.2&lt;/code&gt; (a blocker for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B.1&lt;/code&gt;) suddenly becomes unclear for you.
Because it’s described in several simple words like “Do X” which are clear in the context of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; but not in the context of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt;.
Solution?
You need to &lt;em&gt;always&lt;/em&gt; keep some additional context together with a single subtask.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability issues&lt;/strong&gt;.
Also pretty obvious: when you have &lt;strong&gt;a lot&lt;/strong&gt; of small subtasks, at some moment they become difficult to find.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk of task duplication&lt;/strong&gt;.
As a consequence of the two previous issues, duplicated subtasks appear periodically.
On the other hand, this could happen to any task tracker, right?&lt;/p&gt;

&lt;p&gt;All of these issues could be mitigated with a good tool.
Here we go to the last drawback!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lack of good tools&lt;/strong&gt;.
I’ve looked at tens of the most popular task management tools, and none of them is close enough to the described functionality.
I’ve even had to &lt;a href=&quot;https://github.com/ahitrin/SiebenApp&quot;&gt;create one&lt;/a&gt; by myself.
It allows for understanding the concepts described here but still lacks a lot of important features.
But if you only need to evaluate the ideas I’ve described here, it may help you.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/sieben.png&quot; alt=&quot;the same diagram as above&quot; /&gt;
&lt;small&gt;The same diagram as above, implemented in SiebenApp&lt;/small&gt;&lt;/p&gt;

&lt;s&gt;There&apos;s definitely at least one TODO app for MacOSX with similar functionality, but I&apos;ve lost a link on it.
I&apos;m sorry.&lt;/s&gt;

&lt;p&gt;&lt;strong&gt;UPD (2022-03-18)&lt;/strong&gt;: &lt;a href=&quot;https://drozdyuk.medium.com/how-to-refactor-large-huge-complex-codebases-f5fa8e1d7ee8&quot;&gt;Here&lt;/a&gt; I’ve found a reference to a similar MacOSX tool &lt;a href=&quot;https://www.eyen.fr/&quot;&gt;TaskHeat&lt;/a&gt;.
But I’m a Linux-boy without MacOS, so I couldn’t check it by myself.
This is an excercise left to a reader.&lt;/p&gt;

&lt;h2 id=&quot;is-it-gantt-diagram&quot;&gt;Is it Gantt diagram?&lt;/h2&gt;

&lt;p&gt;The one who has enough experience in development may feel suspicious after my claim: “we have lack of tools”.
Of course, there is a whole class of tools for “&lt;a href=&quot;https://en.wikipedia.org/wiki/Gantt_chart&quot;&gt;management of tasks and dependencies&lt;/a&gt;” we have to live with for decades.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/lists-vs-fractals/example_gantt_chart.gif&quot; alt=&quot;gantt chart example&quot; /&gt;
&lt;small&gt;Source: &lt;a href=&quot;https://upload.wikimedia.org/wikipedia/commons/7/73/Pert_example_gantt_chart.gif&quot;&gt;Wikipedia&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Often, it’s too far from a symbiosis.
The following short story perfectly illustrates the problem:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;When I first started contracting in London, I worked as the only developer on a team that had TWO project managers - one in Leeds and one in Swindon. Every week I dropped working software, they tested it, and the plan changed.&lt;/p&gt;&amp;mdash; Jason Gorman (only, more indoors than usual) (@jasongorman) &lt;a href=&quot;https://twitter.com/jasongorman/status/1427582525724151809?ref_src=twsrc%5Etfw&quot;&gt;August 17, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Every Friday I had to travel up to Leeds and then down to Swindon to help them update their project plan. That was a day out of every week for 3 people to change a Gantt chart that was out of date by the following Tuesday.&lt;/p&gt;&amp;mdash; Jason Gorman (only, more indoors than usual) (@jasongorman) &lt;a href=&quot;https://twitter.com/jasongorman/status/1427582527569637379?ref_src=twsrc%5Etfw&quot;&gt;August 17, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I&amp;#39;ve believed ever since that detailed long-term planning in s/w is a waste of time. If you iterate working software fast enough, you can deal in reality, not guesswork.&lt;/p&gt;&amp;mdash; Jason Gorman (only, more indoors than usual) (@jasongorman) &lt;a href=&quot;https://twitter.com/jasongorman/status/1427582529188597763?ref_src=twsrc%5Etfw&quot;&gt;August 17, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;And, yes, that means they spent 20% of their entire development budget (i.e., me) maintaining a Gantt chart.&lt;/p&gt;&amp;mdash; Jason Gorman (only, more indoors than usual) (@jasongorman) &lt;a href=&quot;https://twitter.com/jasongorman/status/1427582530765705223?ref_src=twsrc%5Etfw&quot;&gt;August 17, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;I have to draw a line between the two approaches.
Why do I distinguish a “fractal-oriented” approach from the well-known Gantt-chart approach?
I’d like to point on the following difference.
Gantt tools are oriented towards managers, not programmers.
They’re mostly focused on very different things (duration, “resources”, “materials”, large scope) and provide a relatively heavyweight workflow.&lt;/p&gt;

&lt;p&gt;Things should change when a planning tool lies in the hands of &lt;em&gt;perfomer&lt;/em&gt;.
When it doesn’t have any distracting or heavyweight concepts.
When it could simply be used in parallel with the work itself without interrupting the flow.
Then complex problems would become much easier to solve.&lt;/p&gt;

&lt;p&gt;Yet at this point, I still have more questions than answers.
How many programmers actually face such kinds of issues (and how often)?
Did I overcomplicate it?
Wouldn’t my remedy be worse than the original disease?&lt;/p&gt;

&lt;p&gt;Let’s leave them open for now.&lt;/p&gt;

&lt;h2 id=&quot;acknowledges&quot;&gt;Acknowledges&lt;/h2&gt;

&lt;p&gt;This article wouldn’t exist without the influence of the following people:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/jessitron&quot;&gt;Jessica Kerr&lt;/a&gt; in her &lt;a href=&quot;https://jessitron.com/2020/09/19/code-is-a-coastline/&quot;&gt;“Code is a coastline”&lt;/a&gt; have inspired me to write my own thoughts about the fractal nature of software tasks.
It’s only my fault that it took several months to formulate an answer!&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/ellnestam&quot;&gt;Ola Ellnestam&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/danielbrolund&quot;&gt;Daniel Brolund&lt;/a&gt; with their &lt;a href=&quot;https://www.manning.com/books/the-mikado-method&quot;&gt;“The Mikado Method”&lt;/a&gt; book have changed my way of thinking about tasks.
After several years of practicing, I find it way more natural, easy and practical than the classical big-ticket-oriented approach.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/SerCeMan&quot;&gt;Sergey Tselovalnikov&lt;/a&gt;, my Russian ex-colleague, gave me much enough courage to start writing in English.
He has also given many useful comments on the draft version of this essay.
Please check out his &lt;a href=&quot;https://serce.me/archives/&quot;&gt;blog&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;additional-links&quot;&gt;Additional links&lt;/h2&gt;

&lt;p&gt;In case you want to take a look at the herd of task trackers, take a look
&lt;a href=&quot;https://opensource.com/article/21/3/open-source-project-management&quot;&gt;here&lt;/a&gt;
or &lt;a href=&quot;https://opensource.com/article/18/2/agile-project-management-tools&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/GeePawHill&quot;&gt;Mr. Michael “GeePaw” Hill&lt;/a&gt; has also posted interesting thoughts on similar topic &lt;a href=&quot;https://www.geepawhill.org/2021/09/29/many-more-much-smaller-steps-first-sketch/&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://www.geepawhill.org/2021/10/26/mmmss-a-closer-look-at-steps/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;comments&quot;&gt;Comments&lt;/h2&gt;

&lt;p&gt;Sorry, comments are currently disabled here.
Once I’ve tried to use Disqus, but it was &lt;em&gt;a kind of mess&lt;/em&gt; that I don’t want to be repeated.&lt;/p&gt;

&lt;p&gt;But any kind of feedback is highly appreciated!
Please leave your comments under the following tweet:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;New blog: Lists vs Fractals &lt;a href=&quot;https://t.co/xt2IsC8YLE&quot;&gt;https://t.co/xt2IsC8YLE&lt;/a&gt;&lt;br /&gt;List-based task-management tools and thinking vs the Fractal nature of programming tasks&lt;/p&gt;&amp;mdash; Andrey Hitrin (@ahitrin) &lt;a href=&quot;https://twitter.com/ahitrin/status/1463527087898255365?ref_src=twsrc%5Etfw&quot;&gt;November 24, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

</content>
 </entry>
 
 <entry>
   <title>Back to Jekyll defaults</title>
   <link href="/jekyll/2021/02/06/back-to-jekyll-defaults/"/>
   <updated>2021-02-06T00:00:00+00:00</updated>
   <id>/jekyll/2021/02/06/back-to-jekyll-defaults</id>
   <content type="html">&lt;p&gt;Hi there!&lt;/p&gt;

&lt;p&gt;I’ve been down for few months - probably because of being overloaded of so much stuff to publish.
Fortunately, this dark period comes to its end.&lt;/p&gt;

&lt;p&gt;But first I want to reset visual blog style.
It’s become too 2012-ish.
It was hard to read, especially on mobile devices.&lt;/p&gt;

&lt;p&gt;Just compare new (left) and old (right) styles of the same page taken from the same device.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/new-and-old.jpg&quot; alt=&quot;new and old look&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Which one is better?&lt;/p&gt;

&lt;p&gt;Interesting, but resetting theme to default was not so trivial.
I’ve tried it several times, but failed.
Jekyll has a &lt;a href=&quot;https://jekyllrb.com/docs/migrations/&quot;&gt;tool&lt;/a&gt; to aid migration from other publishing systems, but not from the old version from itself!
The only working way was a total “rewrite from scratch”:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Delete all old Jekyll files (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_includes&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_layouts&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assets&lt;/code&gt; and few others)&lt;/li&gt;
  &lt;li&gt;Copy new ones created by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll new&lt;/code&gt; command&lt;/li&gt;
  &lt;li&gt;Fix all important issues and deal with others&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now it’s time to back to more interesting content.
I’ll be back soon.
Please stay tuned.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>A model of small decisions</title>
   <link href="/work/2020/11/03/a-model-of-small-decisions/"/>
   <updated>2020-11-03T00:00:00+00:00</updated>
   <id>/work/2020/11/03/a-model-of-small-decisions</id>
   <content type="html">&lt;p&gt;&lt;img src=&quot;/images/a-model-of-small-decisions/forest.jpg&quot; alt=&quot;a dence forest&quot; /&gt;
&lt;small&gt;Would you like to walk here at night? Photo: Andrey Hitrin&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Sometimes thoughts of our human nature makes me sad.
Our attempts to share own thoughts to others can be compared with attempts to find a way out from the dense forest during a moonless night aimed only with a laser pointer.
The only thing you could show to others is a little spot of light randomly jumping back and forth, appearing and disappearing spontaneously.
How could you find a trail with such a weak tool?
How could you convince your party to follow it?
How could you understand if this trail should take you out of the forest?&lt;/p&gt;

&lt;p&gt;That’s why people try to invent new and new words to explain same conceptions.
Everyone hopes that his/her explanations will be good enough to make other understand “the inner nature of things”.
Most of the time it’s worthless: others don’t see more than chaotically jumping light spot.&lt;/p&gt;

&lt;p&gt;Nevertheless, here is my own attempt.
Words written here are not the truth, they are nothing more than my biased reflection of it, based on my limited experience.
I don’t know if they are contains some wisdom, or just a bunch of useless commonplace.
That’s you who will judge.&lt;/p&gt;

&lt;h2 id=&quot;the-quest-for-mastery&quot;&gt;The quest for mastery&lt;/h2&gt;

&lt;p&gt;As usual, I want to speak about programming.&lt;/p&gt;

&lt;p&gt;There is an idea that programming has many similarities with martial arts or musical performing.
In all of these areas you could show some results after relatively little amount of practice, but it requires years of studying and training if you want to achieve truly significant level.
Often you even have to perform at the edge of your own abilities.&lt;/p&gt;

&lt;p&gt;And when you want to perform at such level, you must be aware of all aspects of your profession.
Today I want to speak about small and often invisible decisions that guide you through your everyday work.&lt;/p&gt;

&lt;h2 id=&quot;small-decisions&quot;&gt;Small decisions&lt;/h2&gt;

&lt;p&gt;In our job, you need to do a lot of small decisions every day.
Just watch after yourself during work process, and soon you will be able to notice them.&lt;/p&gt;

&lt;p&gt;Imagine you’ve just started to work on some feature.
Small decisions appear instantly.
How will you start you work?
By reading documentation, or by checking code out, or by asking your colleagues?
Change some code first, or think about test cases?
Implement straightforward change, or roll out few refactorings beforehand?&lt;/p&gt;

&lt;p&gt;How will you deal with “bad code” challenging your way: ignore it, or try to fix right now, or defer a fix for a “better time”?
How will you act when feeling struck: ask your teammate (and which one, when you have several of them), google your problem, stackoverflow it?
Or maybe simply wait until an answer forms inside your head (also known as ‘procrastinating’)?
Or maybe wait until someone asks you about the progress?&lt;/p&gt;

&lt;p&gt;That’s what I mean by the “small decisions” term.
A lot (tens, or even hundreds per day) micro-choices you make in your work.
Sometimes you make this choice consciously, but often not!
You choose your path without even thinking about it, without even noticing the fact of choosing.&lt;/p&gt;

&lt;p&gt;Does this matter?
I think it does.&lt;/p&gt;

&lt;h2 id=&quot;choosemove-dichotomy&quot;&gt;Choose/move dichotomy&lt;/h2&gt;

&lt;p&gt;Let’s draw your way through the imaginary “work task”.
I like to draw graphs, so it’s depicted as a graph.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/a-model-of-small-decisions/01_a_to_b.jpg&quot; alt=&quot;one way from Start to Goal&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Every node here is a small choice you’ve made.
Arrows represent the “movement” between them.
A number of intermediate steps is arbitrary and depends on your own definition of “small decision”.
When you zoom out, they almost disappear.
When you zoom in, a single decision could be even as small as “&lt;em&gt;which finger should hit the given keyboard button?&lt;/em&gt;”.
Here I choose something intermediate.&lt;/p&gt;

&lt;p&gt;And now let’s imagine more possible moves that also could solve your task.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/a-model-of-small-decisions/02_a_to_b.jpg&quot; alt=&quot;many ways from Start to Goal&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here we see a whole net of possible decisions.
What would happen if you choose another step at the start?
Some of alternative ways could be shorter (containing less hops), some of them could be much, much longer!
Of course, here I simplify the problem &lt;strong&gt;a lot&lt;/strong&gt;.
But I need this simplification to show you few important things.
Here are following key points:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;You have to make a lot of decisions&lt;/strong&gt;.
There are points in almost every task that requires your decision.
For example, you need to ask yourself at least once: “&lt;em&gt;have I reached my goal?&lt;/em&gt;”.
And then either get done with it or continue to work.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Even small decisions may have big impact&lt;/strong&gt;.
When you choose the wrong path at start, it may lead you far away from the goal.
You have to move along the non-optimal path or return back to the start.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Every decision takes your time and energy&lt;/strong&gt;.
Usually delay on decision come from one of two or three sources.
The first source is &lt;strong&gt;delay between a question and an answer&lt;/strong&gt;.
Say you’re struck and don’t know where to move next.
You try to ask your colleague via IM for help.
But currently he/she is busy, and only can answer you in 20 minutes.
This delay is the cost of your decision.
The second source is &lt;strong&gt;delay on choosing&lt;/strong&gt; by itself.
It may happen when you see two or more alternative paths and hesitate which one should be followed on (maybe a “frustration” word is suitable here).
The third one is &lt;strong&gt;a need to remember&lt;/strong&gt; a known, but currently forgotten solution (more on this in the next chapter).&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of these make me conclude that it’s important to consider the impact of decision making to our work.
Low-quality decisions reduce your productivity &lt;em&gt;every day&lt;/em&gt;.
They make you stray in the dark without help.&lt;/p&gt;

&lt;p&gt;Surprisingly, even after reading a lot of books and articles on programmer’s productivity, I haven’t found enough much attention to this theme (please correct me if I’m wrong!).
That’s the main reason why I’ve started to write this article.&lt;/p&gt;

&lt;h2 id=&quot;a-table-of-decision-rules&quot;&gt;A table of decision rules&lt;/h2&gt;

&lt;p&gt;OK, maybe quality of decisions is important.
But how could we ever manage them?
Here I suggest a simple &lt;em&gt;model&lt;/em&gt;.
As any other model, it doesn’t describe things as they are, but uses simpler (and more manageable) view on it instead.
The main power of modeling is its ability to predict effects of our actions, but we should never forget about its boundaries.
Outside boundaries, our model will be wrong - and I don’t know where they are.
I sincerely hope that your feedback could help determine them.&lt;/p&gt;

&lt;p&gt;The model is heavily inspired by the work [1].
Think for a minute: this paper is already 40 years old!
Why no one still haven’t developed it into the similar direction as I did?
I don’t know (or maybe I’m just wrong - please let me know in that case).&lt;/p&gt;

&lt;p&gt;This prelude was necessary - but now let’s proceed to the model.
So, suppose each of us have some kind of &lt;em&gt;table&lt;/em&gt; in the head (I warned you, it’s a simplified view).
It contains two columns:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Trigger&lt;/strong&gt;.
An external stimulus that could activate current “table row” or &lt;em&gt;rule&lt;/em&gt;.
For example: a piece of code you’re looking at; a letter from CI server; a message from your colleague; your current thoughts about your task; and so on.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Acton&lt;/strong&gt;.
A thing you do when the &lt;em&gt;rule&lt;/em&gt; is being activated.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It may look like this:&lt;/p&gt;

&lt;table&gt;
&lt;colgroup&gt;
&lt;col width=&quot;35%&quot; /&gt;
&lt;col width=&quot;5%&quot; /&gt;
&lt;col width=&quot;60%&quot; /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr class=&quot;header&quot;&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;You need to find the source of a bug&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git bisect&lt;/code&gt; to find a commit where it was introduced. Then analyse the code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You want to check if your code is ready to deploy&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Push changes to remote repository and run CI service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You want to check if your code is ready to deploy&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Push changes to remote repository and ask someone to review it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You have detected the &quot;smell of ugly code&quot;&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Try to refactor it out immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You have detected the &quot;smell of ugly code&quot;&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Ignore it: we don&apos;t have time to refactor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selenium test on CI server has failed&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Look at screenshot to check where is the problem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selenium test on CI server has failed&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Look at job logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selenium test on Ci server has failed&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Launch that job again: maybe it&apos;s just flaky test?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You&apos;re struck and don&apos;t know what to do next&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Ask your teammates for help&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You&apos;re struck and don&apos;t know what to do next&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Wait until someone asks you what&apos;s going on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This table has important properties:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;It &lt;strong&gt;changes over time&lt;/strong&gt;, depending on your own experience and knowledge.
When you discover new tricks, they have a chance to hold in the table.
In other hand, even the best practices without repetition pass away from your memory.
Of course, they do not always being erased completely.
Rather, they are removed from “the cache”, the fastest part of your memory.
And you’ll have to make an effort of remembering to bring it back.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;It has &lt;strong&gt;limited size&lt;/strong&gt;.
You cannot know literally everything.
You cannot have the best solution for every possible situation you may face.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;It may have &lt;strong&gt;several rules for one situation&lt;/strong&gt;.
In that case your final choice may depend on current context.
Sometimes you may even need to spend additional time and effort to make a choice between alternative actions (“resolve a conflict”).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;It models &lt;strong&gt;only small decisions&lt;/strong&gt;.
Only little choices that often even pass your spotlight and perform automatically could be modelled that way.
In terms of “Thinking, Fast and Slow” [2], it relies to the “System 1” only.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There could be different sources where these rules come from:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Your previous successful and unsuccessful experience.&lt;/li&gt;
  &lt;li&gt;Observations of your teammates: how do they behave in different situations.&lt;/li&gt;
  &lt;li&gt;Direct rules of the project you’re working on (like “use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./gradlew check&lt;/code&gt; to verify correctness of your code”).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope to write more on it in following articles, but the current one has grown big enough.
Seems like I have to go to conclusions.&lt;/p&gt;

&lt;h2 id=&quot;any-benefits&quot;&gt;Any benefits?&lt;/h2&gt;

&lt;p&gt;What benefits could the awareness about these rules bring to you?&lt;/p&gt;

&lt;p&gt;First of all, you should take into consideration the limited size of your memory.
In order to make better decisions, you need to consciously “tune” your rule table.
How could it be done?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Remember your good (effective) decision rules&lt;/strong&gt;.
Practice them from time to time so they don’t leave your working memory.
Write them down in known place so they could be remembered effectively when needed.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Try to &lt;strong&gt;free your memory from unneeded decision rules&lt;/strong&gt;.
If they aren’t needed, forget them.
If they still may be useful, keep them in an external place.
For example, imagine you have a complex task management ceremony consisted of 7-8 steps that needs to be performed once a week.
You should not try to keep this ceremony in your memory.
Writing it in a form of simple instruction or checklist is much better.
Now, you need to remember the only one thing: a place where your checklist lies.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Try to &lt;strong&gt;avoid multitasking&lt;/strong&gt;.
Different tasks require different decision rules.
When you switch back and forth from one task to another, these rules fight for a place in your working memory.
There is no guarantee they all could fit inside altogether.
It’s highly likely they couldn’t.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Review your working habits&lt;/strong&gt; regularly.
Are they effective enough?
Have some of them become obsolete?
Circumstances change over time, and your habits that were effective a year or two ago, now could pull your performance down.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Check out &lt;strong&gt;how much time to you spend on conflict resolving&lt;/strong&gt; between several possible actions.
Try to extract strict and distinct rules which action in which case you should prefer.
This should decrease your waste of time and energy the next time you face similar dilemma.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, if we back for a while to the first part of the model (“choose/move”):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Try to &lt;strong&gt;minimize amount of decisions&lt;/strong&gt; you need to take during one task.
Split big tasks into smaller and well defined steps, so you need to make few small decisions between them.
Remove obstacles that pulls you or your teammates away from the streamline (dirty code, small but noisy bugs and so on).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Increase speed of your tools&lt;/strong&gt;.
The faster they give feedback, the less time you waste on waiting.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Remember: &lt;strong&gt;there is another way&lt;/strong&gt; almost everywhere.
When you feel struck and cannot see any good next move toward your goal, think for a while.
Maybe there is a way for you, but your &lt;em&gt;thought patterns&lt;/em&gt; prevent you to spot it?&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An important role both in professional music and in martial arts plays the concept of “minimizing waste of energy”.
Fingers of a guitar master don’t flicker spontaneously, they are fully controlled.
A kung fu master doesn’t hit randomly.
Neither should you.&lt;/p&gt;

&lt;p&gt;Look at the real master of small decisions!
&lt;img src=&quot;/images//a-model-of-small-decisions/Bruce_Lee.jpg&quot; alt=&quot;Bruce Lee&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Bruce Lee. Image from &lt;a href=&quot;https://en.wikipedia.org/wiki/Bruce_Lee&quot;&gt;Wikipedia&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Watch after his movements from tournament records.
He seems to move slow and even lazy, but no one can beat him - because he performs no excess movement, no excess small decision.&lt;/p&gt;

&lt;h2 id=&quot;the-final-words&quot;&gt;The final words&lt;/h2&gt;

&lt;p&gt;For me, the described model have become surprisingly fruitful.
It very well “describes” many effects and problems observed in the IT field.
I’m curious why does it happen: either because it’s really good and predictive or because it’s way too fuzzy and allows to gain any answer you want (e.g. it’s actually useless).
That’s why I want to provide it onto your court.&lt;/p&gt;

&lt;p&gt;What I’d like to cover next:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Gaining and losing experience.
Is it always good to know a lot?&lt;/li&gt;
  &lt;li&gt;Empathy.
How could this model help you understand others’ behavior and improve communication?&lt;/li&gt;
  &lt;li&gt;Team performance.
So called “10x programmers”, pair and mob programming, an effect of diversity.&lt;/li&gt;
  &lt;li&gt;Clean code, dirty code, legacy code.
How to deal with entropy.&lt;/li&gt;
  &lt;li&gt;Maybe something more…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you like, try to apply the model to given themes for yourself.
Will our conclusions match?&lt;/p&gt;

&lt;p&gt;I will be very glad to receive your feedback on this topic!
Is this model useful for you?
Does it provide wrong results?
Is this article way too abstract?
Is it filled with obvious things?&lt;/p&gt;

&lt;p&gt;Comments on this blog are not available yet (sorry!), so I’d like to ask you leave comments under the following tweet:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;My new blog on programmer productivity: &amp;quot;A model of small decisions&amp;quot; &lt;a href=&quot;https://t.co/Pq5ErHztk6&quot;&gt;https://t.co/Pq5ErHztk6&lt;/a&gt;&lt;/p&gt;&amp;mdash; Andrey Hitrin (@ahitrin) &lt;a href=&quot;https://twitter.com/ahitrin/status/1323557869984370688?ref_src=twsrc%5Etfw&quot;&gt;November 3, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;“Models of Competence in Solving Physics Problems”. Jill H. Larkin, John McDermott, Dorothea P. Simon, and Herbert A. Simon&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Thinking, Fast and Slow”. Daniel Kahneman.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>A restart in English</title>
   <link href="/misc/2020/09/30/a-restart-in-english/"/>
   <updated>2020-09-30T00:00:00+00:00</updated>
   <id>/misc/2020/09/30/a-restart-in-english</id>
   <content type="html">&lt;p&gt;Well, this blog seems to stay abandoned for a while.
But now I want to launch a new experiment.&lt;/p&gt;

&lt;p&gt;My native language is Russian, but the most interesting things in IT happens in English-speaking community.
Writing in Russian means writing only for 1% of potential readers.
But I have ideas to share, and I want them to be published.&lt;/p&gt;

&lt;p&gt;Starting from this moment, I will try to publish new posts in English!
For me, it should be a new motivating challenge, for community, new interesting thoughts (hope so).&lt;/p&gt;

&lt;p&gt;A quick overview of further plans:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: What kind of content do you want to publish?&lt;/p&gt;

&lt;p&gt;Mostly new thoughts and ideas (I do have some), and maybe few translations of old posts in Russian (only for the best ones).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: What about comments? Will they appear?&lt;/p&gt;

&lt;p&gt;It’s quite possible!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: New posts in Russian?&lt;/p&gt;

&lt;p&gt;Not sure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: Will you separate Ru and En parts?&lt;/p&gt;

&lt;p&gt;Not sure. Only in case it’s requested.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Я перешёл в Яндекс</title>
   <link href="/work/2020/04/28/yandex/"/>
   <updated>2020-04-28T00:00:00+00:00</updated>
   <id>/work/2020/04/28/yandex</id>
   <content type="html">&lt;p&gt;Собственно, вся новость в заголовке.&lt;/p&gt;

&lt;p&gt;Я 9 лет работал в Naumen.
Было там много хорошего, были и плохие моменты, но в целом итог положительный.
Тем не менее, 9 лет на одном месте (даже с учётом неоднократных внутренних переходов) - это достаточно для того, чтобы наработаться.&lt;/p&gt;

&lt;p&gt;Я решил двигаться дальше, искать новый опыт и новые челленджи.
И сейчас оказался в команде Яндекс.Маркет.
Посмотрим, что будет дальше!&lt;/p&gt;

&lt;p&gt;В этом блоге давно не было записей.
Может быть, переход позволит высвободить на них чуть больше времени - или даст новое вдохновение.
Так или иначе, постараюсь, чтобы в этом году было побольше записей!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>SiebenApp: мой персональный проект</title>
   <link href="/2018/01/24/siebenapp/"/>
   <updated>2018-01-24T00:00:00+00:00</updated>
   <id>/2018/01/24/siebenapp</id>
   <content type="html">&lt;p&gt;Вот уже больше года я потихоньку развиваю &lt;a href=&quot;https://github.com/ahitrin/SiebenApp&quot;&gt;свой pet project&lt;/a&gt;.
Свободного времени на него находится не так уж много, но постепенно проект всё же почти дозрел до уровня бета-версии.&lt;/p&gt;

&lt;p&gt;Это персональный десктопный менеджер задач, написанный на Python+Qt.
В наши дни менеджеров задач для GTD можно найти в интернете сколько угодно, но у моего есть свои киллер-фичи.
Ради них я и взялся за разработку, хотя на это раскачивался очень долго, стараясь использовать существующие решения.
В чём же его главная особенность?&lt;/p&gt;

&lt;p&gt;Работая программистом, я постоянно сталкиваюсь с &lt;strong&gt;сложными&lt;/strong&gt; задачами.
Эта сложность складывается из разных факторов:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Большое количество подзадач&lt;/strong&gt;: написать кусок функциональности, провести некоторый рефакторинг, проверить гипотезу, обсудить с кем-то вопрос, и так далее.&lt;/li&gt;
  &lt;li&gt;Непредсказуемая &lt;strong&gt;глубина задач&lt;/strong&gt;. Не так уж редко оказывается, что простой, на первый взгляд, этап оборачивается отдельной крупной задачей с десятками подзадач.&lt;/li&gt;
  &lt;li&gt;Нетривиальные &lt;strong&gt;зависимости между этими подзадачами&lt;/strong&gt;. Не все из них можно выполнять в произвольном порядке. Часто бывает так, что один шаг блокирует несколько других. Типичный пример - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TODO&lt;/code&gt;-решение в коде, когда вместо “правильного” решения ставится “костыль” (потому что для “правильного” решения не хватает времени или информации). В этом случае мы не планируем вводить “правильное” решение, пока не получим нужную информацию, или пока не завершим более приоритетные шаги текущей задачи.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Исследовательский характер&lt;/strong&gt; многих задач. Заранее неизвестно, какой путь к цели является правильным. Приходится пробовать несколько вариантов решения, прежде чем найдётся подходящий.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;За фасадом типичных современных таск-менеджеров (по крайней мере, тех, что попадались мне) прячется простая абстракция: &lt;strong&gt;плоский список задач&lt;/strong&gt;.
Чтобы немного облегчить жизнь, эти списки можно некоторым образом группировать.
Например, карточка в Trello может служить контейнером для нескольких списков.
Это достаточно неплохо работает для небольших задач, но и только.&lt;/p&gt;

&lt;p&gt;Как, например, работать в том же Trello с задачей, которая из нескольких начальных шагов разрастается до нескольких десятков и длится несколько недель (да пусть даже несколько дней)?
Я пробовал применять разные подходы.
Если держать всё в одной карточке, то она надолго застревает в списках “Делается”, чем сильно демотивирует.
Плюс, становится неудобно работать с подзадачами.
Если разбивать задачу на несколько карточек, то теряется связь между ними.
Сложнее становится понимать, сколько сделано, а сколько осталось.&lt;/p&gt;

&lt;p&gt;В какой-то момент я решил перестать обманывать себя и не пытаться превращать &lt;strong&gt;граф подзадач&lt;/strong&gt; в список.
А вместо этого стал работать с ним именно как с графом.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/siebenapp/example.jpg&quot; alt=&quot;пример реального графа&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Вдохновением для этого послужила &lt;a href=&quot;https://www.manning.com/books/the-mikado-method&quot;&gt;книжка про Mikado Method&lt;/a&gt;, который я пробовал применять для рефакторинга.
Хорошее описание на русском есть, к примеру, &lt;a href=&quot;http://www.maxshulga.ru/2012/03/mikado-legacy-code.html&quot;&gt;здесь&lt;/a&gt;.
А начальным инструментом для рисования графа задач стал прекрасный &lt;a href=&quot;http://www.graphviz.org&quot;&gt;graphviz&lt;/a&gt;.
Собственно говоря, сам &lt;a href=&quot;https://github.com/ahitrin/SiebenApp&quot;&gt;SiebenApp&lt;/a&gt; в режиме работы по-умолчанию до сих пор использует graphviz для рендеринга.
Хотя это уже скоро изменится.&lt;/p&gt;

&lt;p&gt;Мои главные успехи последней недели - это появление более-менее приемлемого “нативного” рендеринга (т.е., средствами Python+Qt, без использования сторонних программ).
Для этого пришлось продраться через пару книг по отрисовке графов и как следует поломать голову на практике.
Математика там не самая тривиальная, шаг влево или вправо - и ты уже будешь решать NP-полную задачу.
Поэтому приходится пробовать разные варианты, опытным путём находить правильный порядок решения задачи, закрывать десятки плодящихся мелких подзадач, а что-то откладывать на потом.&lt;/p&gt;

&lt;p&gt;В общем, делать именно то, для чего мне был нужен SiebenApp.
Не лишним будет добавить, что управление его развитием я вёл в нём же буквально с первой недели, как взялся за работу.
Dog fooding - это реально полезная вещь!
Позволяет лучше понимать, в какую сторону следует развивать проект.&lt;/p&gt;

&lt;p&gt;Думаю, в ближайшие несколько недель я улучшу нативный рендеринг, и тогда можно будет окончательно отказаться от использования graphviz.
Ещё пара улучшений - и приложение можно будет считать готовым для комфортного бета-тестирования.
Хотя, по факту, смельчаки могут пробовать его уже сейчас!&lt;/p&gt;

&lt;p&gt;Подробная инструкция по запуску и использованию есть в README на &lt;a href=&quot;https://github.com/ahitrin/SiebenApp&quot;&gt;странице проекта&lt;/a&gt;.
Плюс, я начал потихоньку вести &lt;a href=&quot;https://ahitrin.github.io/SiebenApp&quot;&gt;англоязычный блог проекта&lt;/a&gt;.
Буду рад услышать любой фидбек по этому проекту!&lt;/p&gt;

&lt;p&gt;Надеюсь, он окажется полезным не только мне :)&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Корректная работа с PostgreSQL в Docker</title>
   <link href="/work/2017/10/02/postgresql-in-docker/"/>
   <updated>2017-10-02T00:00:00+00:00</updated>
   <id>/work/2017/10/02/postgresql-in-docker</id>
   <content type="html">&lt;p&gt;Интересную проблему удалось решить вчера.&lt;/p&gt;

&lt;p&gt;Вот уже несколько месяцев мы в Naumen Phone используем Docker для создания окружения для сборки и тестирования приложений.
Технология для этих целей подходит отлично, экономит кучу времени и нервов по сравнению с тем, что было раньше.
Образ контейнера создаётся на билд-сервере по рецепту из репозитория (с код-ревью и историей изменений).
Он получается по-настоящему иммутабельным - а значит, гарантируется воспроизводимость поведения при локальных запусках и на билд-сервере.
Он запускается практически мгновенно, а значит, контейнер не жалко убить и пересоздать заново.
На него можно довольно легко писать автотесты (например, на &lt;a href=&quot;http://testinfra.readthedocs.io/en/latest/&quot;&gt;Testinfra&lt;/a&gt;), которые тоже выполняются быстро.&lt;/p&gt;

&lt;p&gt;Но, как и везде, возникают свои нюансы.&lt;/p&gt;

&lt;p&gt;Так как мы перешли на Docker с виртуализованного окружения (Virtualbox/KVM/Vagrant), то многие вещи делаем по старинке, как привыкли на виртуалках.
В частности, мы используем для интеграционных автотестов большой “сборный” образ, в котором установлена и СУБД, и пачка наших собственных сервисов, и даже целый systemd (через который запускается всё это хозяйство).
Конечно, это далеко от тех же принципов &lt;a href=&quot;https://12factor.net/ru/&quot;&gt;12 factor app&lt;/a&gt;, но в целом в подход &lt;a href=&quot;https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#each-container-should-have-only-one-concern&quot;&gt;each container should have only one concern&lt;/a&gt; вписывается.
Да и User Experience для разработчиков остаётся почти такой же, как был на VBox+Vagrant, что немаловажно.&lt;/p&gt;

&lt;p&gt;Но вот была одна странная проблема, которая долго пила нам кровушку.
Почему-то при первом запуске контейнера далеко не сразу становился доступен сервис PostgreSQL.
Он, вроде бы, и запускался быстро, и соединения принимал - но затем клиенты отбивались на этапе аутентификации.
И сообщение об ошибке мало что говорило нам:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    ВАЖНО:  система баз данных запускается
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Это безобразие продолжалось порой по несколько минут, после чего сервер наконец-то становился паинькой и соглашался работать.
Вот только далеко не все клиенты были к этому времени готовы работать с ним.
То один, то другой сервис не дожидался старта СУБД и отваливался по таймауту.
Приходилось ставить костыль в виде shell-скрипта, который дожидается полного запуска PG и после этого перезапускает наши сервисы.
При запуске в Vagrant это выглядело сурово:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/postgresql-in-docker/vagrant.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Однако, как ни странно, если контейнер после работы не удалялся, а просто останавливался (через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vagrant halt&lt;/code&gt;), то при повторных его запусках проблема уже не повторялась.
Старт проходил быстро, и сервисы подключались к базе без проблем.&lt;/p&gt;

&lt;p&gt;Это наводило на мысль, что можно попытаться заставить СУБД сделать при создании что-то такое, что она делает во время работы контейнера, чтобы при следующем запуске быстро подняться.
Только было непонятно, что же именно делать.
Гуглёж по сообщению об ошибке не находил ничего, что наводило бы на суть проблемы.&lt;/p&gt;

&lt;p&gt;Пришлось расчехлять &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strace&lt;/code&gt; и смотреть, чем заняты процессы СУБД.
Оказалось, что они открывают и закрывают большое количество файлов, в которых СУБД, собственно, и хранит все свои данные.
Я включил фантазию и начал гуглить всякие конструкции типа “postgresql slow startup”, “postgresql startup base files” и т.п.
По ссылкам встречалось упоминание recovery mode, так что я начал шерстить мануалы на эту тему.&lt;/p&gt;

&lt;p&gt;И внезапно нашёл в &lt;a href=&quot;https://www.postgresql.org/docs/9.6/static/app-pg-ctl.html&quot;&gt;документации&lt;/a&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pg_ctl&lt;/code&gt; &lt;strong&gt;это&lt;/strong&gt;…&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pg_ctl stop [-W] [-t seconds] [-s] [-D datadir] [-m s[mart] | f[ast] | i[mmediate]]

In stop mode, the server that is running in the specified data directory is
shut down. Three different shutdown methods can be selected with the -m
option.  “Smart” mode waits for all active clients to disconnect and any
online backup to finish. If the server is in hot standby, recovery and
streaming replication will be terminated once all clients have
disconnected.  “Fast” mode (the default) does not wait for clients to
disconnect and will terminate an online backup in progress. All active
transactions are rolled back and clients are forcibly disconnected, then
the server is shut down.  “Immediate” mode will abort all server processes
immediately, without a clean shutdown. This will lead to a crash-recovery
run on the next restart.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Если перевести на русский, то у PostgreSQL есть три режима завершения работы:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;smart&lt;/code&gt;: дожидаться завершения работы всех клиентов, а затем делать бэкап&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fast&lt;/code&gt;: отбивать всех клиентов, а затем делать бэкап (режим по-умолчанию)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;immediate&lt;/code&gt;: отбивать всех клиентов и завершаться без бэкапа&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ключевая фраза оказалась в самом конце абзаца: “this will lead to crash-recovery run on the next restart”.&lt;/p&gt;

&lt;p&gt;Если запустить PG на “железной” машине или внутри обычной виртуалки, то при корректном завершении работы машины СУБД получит от &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt; соответствующий сигнал и остановится в режиме &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fast&lt;/code&gt;.
Но в docker работа завершается через брутальное убийство всего дерева процессов, начиная с контейнерного &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt;.
Разумеется, в этой ситуации PG не имеет возможности сделать бэкап.
Поэтому мы и получаем “crash-recovery run” при следующем запуске.
И пока СУБД не проверит полностью всё своё добро на диске, пока не убедится в корректности данных (или не починит некорректные данные), ни одному клиенту не позволительно подключаться к серверу.
Но после прогона этого восстановления база оказывается в консистентном состоянии, поэтому следующие запуски осуществляются быстро.&lt;/p&gt;

&lt;p&gt;Значит, чтобы ускорить самый первый запуск контейнера, мы должны дать PostgreSQL возможность сделать online backup при сборке образа.&lt;/p&gt;

&lt;p&gt;Проверить эту гипотезу оказалось совсем несложно, так как контейнер создаётся из исходного кода.
Я просто добавил одну команду в самом конце скрипта:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;su - postgres -c &apos;/usr/pgsql-9.6/bin/pg_ctl stop&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Собрал образ локально, запустил его через Vagrant, и - о чудо! - теперь всё запускается моментально!&lt;/p&gt;

&lt;h2 id=&quot;полученные-уроки&quot;&gt;Полученные уроки&lt;/h2&gt;

&lt;p&gt;Очевидный вывод из этой истории может звучать так: know your tools.
Не думаю, что наша проблема показалась бы сложной для эксперта по PG.
Но… мы не волшебники, мы только учимся.
И в том числе на таких вот ошибках - довольно эффективный способ, между прочим :)&lt;/p&gt;

&lt;p&gt;Ещё один показательный пример: в проблеме помог разобраться &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strace&lt;/code&gt;.
Хотя он и не указал на источник проблемы явным образом, но задал правильное направление для дальнейших поисков.
Это безусловное преимущество открытой инфраструктуры GNU/Linux: ни один процесс, запущенный на машине, не является “чёрным ящиком”.
Используя правильные инструменты, можно увидеть, чем именно он занят, даже не используя исходники.
Хотя, если надо, качай исходники и их тоже используй - красота же!&lt;/p&gt;

&lt;p&gt;Ну и не удержусь, порадуюсь ещё раз, что мы наводим порядок в своей инфраструктуре, используя подход infrastructure as code.
Я уже наелся за прошлые годы проблем с мутабельными, настраиваемыми вручную виртуалками, проблемы с которыми вопроизводятся ненадёжно, так что нынешнее положение дел радует меня очень и очень.&lt;/p&gt;

&lt;p&gt;Чего и вам желаю, мои уважаемые читатели!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt;: а мы пойдём вычищать из кода костыли…&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Профайлинг нагруженных Python-программ: печальная история со счастливым концом</title>
   <link href="/work/2017/05/29/python-profiling/"/>
   <updated>2017-05-29T00:00:00+00:00</updated>
   <id>/work/2017/05/29/python-profiling</id>
   <content type="html">&lt;p&gt;Профилирование - это первое, что должно приходить в голову, когда у нас встаёт вопрос производительности приложения. Если грамотно провести профилирование приложения, то суть проблем произвдительности становится намного понятнее, и фиксы можно создавать уже более осмысленно.&lt;/p&gt;

&lt;p&gt;Однако в мире Python с профилированием не всё так прекрасно. Существует несколько инструментов, но все из них обладают определёнными ограничениями.&lt;/p&gt;

&lt;h2 id=&quot;cprofile-и-его-друзья&quot;&gt;cProfile и его друзья&lt;/h2&gt;

&lt;p&gt;Как мы все знаем, в Python есть встроенный профайлер в лице модуля &lt;a href=&quot;https://docs.python.org/2/library/profile.html#module-cProfile&quot;&gt;cProfile&lt;/a&gt;. Он существует давно, поставляется вместе с интерпретатором, прост в обращении. Разве не это ли нужно для счастья?&lt;/p&gt;

&lt;p&gt;Всё было бы славно, если бы не один нюанс. Дело в том, что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cProfile&lt;/code&gt; - детерминированный профайлер (deterministic profiler). Это значит, что он профилирует &lt;strong&gt;все&lt;/strong&gt; методы. И на вызов каждого метода &lt;a href=&quot;https://docs.python.org/2/library/profile.html#limitations&quot;&gt;накладывается некоторое дополнительное время&lt;/a&gt;. Достаточно часто это не является проблемой. Но, если мы тестируем приложение, работающее под серьёзной нагрузкой, возникают неприятные спецэффекты.&lt;/p&gt;

&lt;p&gt;На днях, запустив профайлер на нагруженный сервис, созданный на основе фреймворка Twisted, я обнаружил странный результат: при вычислении итогового времени работы методов получалось, будто бы больше всего времени (90%) сервис проводил внутри… &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;epollreactor.doPoll&lt;/code&gt;. Вот какую картинку показывает &lt;a href=&quot;https://github.com/jrfonseca/gprof2dot&quot;&gt;grpof2dot&lt;/a&gt; (цифра 88.37% справа внизу):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/python-profiling/epoll.jpg&quot; alt=&quot;pic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Почему это странно? Потому что системный вызов &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;epoll_wait&lt;/code&gt; работает &lt;strong&gt;быстро&lt;/strong&gt;! Приложение не может проводить в нём так много времени. Это подтверждается и независимой проверкой через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strace&lt;/code&gt;. Обращений к &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;epoll_wait&lt;/code&gt; происходит много, но суммарное время, которое тратится на эти обращения, весьма мало.&lt;/p&gt;

&lt;p&gt;Но не в том случае, когда мы запускаем программу под детерминированным профайлером! Профайлер добавляет свой overhead на вычисление времени работы метода, сумма этих overhead-ов накапливается и становится больше, чем время работы всех остальных методов. Проблемы тут возникает сразу две:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Приложение начинает работать под профайлером совсем не так, как без него. Оно действительно больше времени тратит на тупление внутри реактора, а на рабочую логику может выделять меньше времени. Соответственно, у него заметно проседает производительность.&lt;/li&gt;
  &lt;li&gt;Результаты профилирования не соответствуют поведению приложения без профайлера. Из его отчёта совершенно неясно, где же на самом деле тратится время.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Конечно, далеко не все Python-программы используют Twisted с его реактором, поэтому данная проблема может быть для них слабо актуальна. Но мне-то что со своим сервисом делать?&lt;/p&gt;

&lt;p&gt;Помимо &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cProfile&lt;/code&gt;, в Python-мире существует ещё несколько разномастных профайлеров, неплохой обзор которых есть &lt;a href=&quot;https://habrahabr.ru/company/mailru/blog/202832/&quot;&gt;на хабре&lt;/a&gt;, но они нам тоже ничем не помогут: тоже детерминированные, тоже добавляют существенный overhead, да ещё и требуют модификации исходного кода сервиса, т.к. профилируют только участки, помеченные специальными декораторами.&lt;/p&gt;

&lt;p&gt;Какие ещё есть варианты? Есть смысл поглядеть в сторону статистических профайлеров.&lt;/p&gt;

&lt;h2 id=&quot;почти-универсальный-солдат-perf&quot;&gt;(Почти) универсальный солдат perf&lt;/h2&gt;

&lt;p&gt;Это классический представитель семейства статистических профайлеров. Работает по такому принципу: N раз в секунду снимает текущий стек у приложения и запоминает его. Затем из всех собранных стеков можно сформировать общую картину: какие методы попадаются чаще, какие реже. Такой подход не даёт видеть редкие вызовы методов, но зато достаточно хорошо показывает, где происходят основные задержки, а также меньше влияет на производительность профилируемого сервиса (и меньше искажает картину). Я использую для этого &lt;a href=&quot;https://github.com/brendangregg/FlameGraph&quot;&gt;FlameGraph&lt;/a&gt;, выходит очень наглядно и удобно.&lt;/p&gt;

&lt;p&gt;В дополнение, частоту сэмплирования можно задавать какую угодно: хочешь уменьшить нагрузку - снижаешь частоту, хочешь увеличить точность - поднимаешь. Особенно это важно при работе с нагруженными сервисами: рост overhead-а от профилирования может серьёзно искажать само поведение профилируемого сервиса, как мы уже убедились в предыдущей части.&lt;/p&gt;

&lt;p&gt;Казалось бы, идеальный инструмент! Но тут возникает новый нюанс: если мы пробуем натравить &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf&lt;/code&gt; на Python-программу, то увидим не стеки своей программы, а стеки &lt;em&gt;самого интерпретатора Python&lt;/em&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf&lt;/code&gt; работает тупо, но честно: “&lt;em&gt;коли мне сказали профилировать интерпретатор, я буду профилировать интерпретатор, а что за код он крутит внутри себя, мне не важно&lt;/em&gt;”. Вот только нам от такой информации немного пользы. Разве что можем дополнительно убедиться, что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cProfile&lt;/code&gt; сильно врёт: показания &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf&lt;/code&gt; говорят нам, что на &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;epoll&lt;/code&gt; тратится вовсе не 90% времени, а какие-то единицы процентов.&lt;/p&gt;

&lt;p&gt;Есть ли инструмент, который работает как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf&lt;/code&gt;, но при этом позволяет видеть методы Python-сервиса? Как оказалось, есть.&lt;/p&gt;

&lt;h2 id=&quot;новая-надежда-pyflame&quot;&gt;Новая надежда: pyflame&lt;/h2&gt;

&lt;p&gt;Инженеры из Uber сделали для нас &lt;a href=&quot;https://github.com/uber/pyflame&quot;&gt;такой инструмент&lt;/a&gt;. Он присоединяется к Python-программе с помощью вызова &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ptrace&lt;/code&gt;, &lt;a href=&quot;http://eng.uber.com/pyflame/&quot;&gt;считывает состояние из структур интерпретатора&lt;/a&gt; и выводит данные в формате, пригодном для построения FlameGraph-ов. Профилирование тоже производится статистически, его частоту можно варьировать. Это именно то, что мне нужно!&lt;/p&gt;

&lt;p&gt;Хотя свои недостатки у этого инструмента тоже есть.&lt;/p&gt;

&lt;p&gt;Во-первых, готовые бинарники есть только под Ubuntu и Arch. Если Вы используете другой дистрибутив (например, любой RPM-based), придётся расчехлять компилятор и собирать приложение вручную. Но это почти тривиально! Сначала ставим нужные для сборки пакеты:&lt;/p&gt;
&lt;pre&gt;
autoconf automake libtool gcc-c++
&lt;/pre&gt;

&lt;p&gt;Затем можно собрать приложение:&lt;/p&gt;
&lt;pre&gt;
./autogen.sh
./configure
make
&lt;/pre&gt;

&lt;p&gt;Если сборка прошла успешно, то в папке &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; появится файл &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyflame&lt;/code&gt;, который и будет работать профилировщиком.&lt;/p&gt;

&lt;p&gt;После этого можно присоединяться к запущенному приложению и добывать из него стеки!&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./src/pyflame -s 60 -r 0.01 &amp;lt;pid&amp;gt; &amp;gt; my_service.flame
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Показал бы тут и итоговый flamegraph, но, боюсь, это уже будет нарушать NDA :) Лучше посмотрите примеры из самого PyFlame или попробуйте построить их сами. Просто скажу, что мы здесь получаем именно то, что было нужно: нормальные питонические стеки с указанием частоты попадания в каждый отдельный из них. Чем шире строчка для функции, тем чаще мы в неё попадали, а значит, тем больше времени уходит на её выполнение.&lt;/p&gt;

&lt;p&gt;Есть ещё пара важных особенностей, про которые надо знать, применяя &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyflame&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;В итоговом графе значительную часть времени может занимать строчка “(idle)”. Как разработчики &lt;a href=&quot;https://github.com/uber/pyflame#what-is-idle-time&quot;&gt;пишут&lt;/a&gt; в документации, это время соответствует тем семплам, когда никакой Python-код не держит GIL (т.е., не выполняется). В это время может происходить другая работа в соседнем потоке, но для &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyflame&lt;/code&gt; она не видна. Её можно сделать видимой, если при запуске указать флаг &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--threads&lt;/code&gt;, но на относительно старых ядрах Linux это &lt;a href=&quot;https://github.com/uber/pyflame/issues/55&quot;&gt;может приводить&lt;/a&gt; к зависаниям профилируемого приложения. Либо включаем опцию на свой страх и риск, либо миримся с наличием &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(idle)&lt;/code&gt;, либо отключаем статистику по нему с помощью флага &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-x&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;В некоторых ситуациях, как мне показалось, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyflame&lt;/code&gt; не выводит никаких данных. Скорее всего, это происходит, если профилируемое приложение внезапно завершилось (например было перезапущено). Либо, если профилирование было очень коротким. Отсюда вытекает рекомендация: период профилирования должен быть достаточно длинным (как минимум, несколько десятков секунд). Впрочем, для статистического профайлера это в любом случае необходимо - иначе его результаты будут не особо точными.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;подводя-итоги&quot;&gt;Подводя итоги&lt;/h2&gt;

&lt;p&gt;Я очень доволен, что удалось найти подходящий инструмент для моих задач. Крайне непродуктивно заниматься оптимизацией производительности программы, не имея возможности напрямую видеть результаты этой оптимизации.&lt;/p&gt;

&lt;p&gt;Но есть и ещё один полезный урок: даже если кажется, что подходящий инструмент найден, совсем не лишней будет проверка корректности его показаний. Именно такая проверка показала, что &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cProfile&lt;/code&gt; не подходит конкретно в моём случае, и вынудила меня искать альтернативы. Посмотрим, насколько правдив окажется и сам &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyflame&lt;/code&gt;. Время покажет!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Комментарии Disqus отключены</title>
   <link href="/test/2017/04/26/comments-disabled/"/>
   <updated>2017-04-26T00:00:00+00:00</updated>
   <id>/test/2017/04/26/comments-disabled</id>
   <content type="html">&lt;p&gt;Я порядком устал за прошедшие годы от возни с настройкой комментариев через Disqus.
Старые комментарии пропали, с новыми непонятно, появляются ли.&lt;/p&gt;

&lt;p&gt;А ещё на Хабре &lt;a href=&quot;https://habrahabr.ru/post/327424/&quot;&gt;пишут&lt;/a&gt; про нехорошее поведение сервиса: тотальная слежка за пользователями, сильное замедление загрузки, перехват кликов и т.п.&lt;/p&gt;

&lt;p&gt;Вот я и подумал - зачем оно мне нужно?&lt;/p&gt;

&lt;p&gt;С этого момента все комментарии в блоге отключаются.
Если в будущем появится адекватный сервис для поддержки комментариев на статических сайтах, попробую его подключить.
А пока что пускай всё остаётся так.&lt;/p&gt;

&lt;p&gt;Для связи со мной можно использовать любой канал со страницы &lt;a href=&quot;https://ahitrin.github.io/whoiam/&quot;&gt;“Кто я такой”&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Рабочий дневник программиста</title>
   <link href="/work/2017/03/25/worklogs/"/>
   <updated>2017-03-25T00:00:00+00:00</updated>
   <id>/work/2017/03/25/worklogs</id>
   <content type="html">&lt;p&gt;&lt;img src=&quot;/images/worklogs/640px-Grand_Turk.jpg&quot; alt=&quot;boat_journal&quot; /&gt;
&lt;small&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Logbook#/media/File:Grand_Turk(34).jpg&quot;&gt;wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Хочу немного рассказать про полезную рабочую практику, которая уже не первый год здорово помогает мне.
Как ни странно, далеко не все разработчики этим пользуются.
Наверно, большинству просто не приходило в голову.
Заполним пробел, проведём сеанс ликбеза.&lt;/p&gt;

&lt;p&gt;Сама идея &lt;strong&gt;рабочего дневника&lt;/strong&gt; очень простая.
Начиная работу над очередной задачей, я завожу в специальной папке новый текстовый файл.
В него пишу все мысли, которые приходят в голову во время работы над этой задачей:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Какую проблему я решаю&lt;/li&gt;
  &lt;li&gt;Какие действия планирую совершить и в каком порядке&lt;/li&gt;
  &lt;li&gt;Какие действия уже совершил, и к чему они привели&lt;/li&gt;
  &lt;li&gt;Какие у меня есть предположения о характере проблемы&lt;/li&gt;
  &lt;li&gt;Какие сложности и непонятки возникают&lt;/li&gt;
  &lt;li&gt;С кем из коллег и по какому вопросу надо поговорить&lt;/li&gt;
  &lt;li&gt;Что мне мешает сделать следующий шаг&lt;/li&gt;
  &lt;li&gt;И прочее в том же духе&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;В общем, стараюсь сохранять всё более-менее полезное.&lt;/p&gt;

&lt;p&gt;Выглядит это обычно как-то так:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/worklogs/worklogs_example.png&quot; alt=&quot;example&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;как-именно-это-помогает&quot;&gt;Как именно это помогает&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Разгружаем голову!&lt;/strong&gt;
Чем больше мыслей нужно держать в памяти одновременно, тем меньше ресурсов остаётся на сами размышления.
Записывать все планы и задачи - базовый принцип техники GTD.
Для разработчиков это особенно актуально: обычно нам приходится иметь дело с очень сложными задачами, которые сложно загрузить в голову целиком.
Скидывая мысли и идеи в дневник, мы освобождаем голову для размышлений.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Проще вспомнить подробности решения задачи.&lt;/strong&gt;
Для многих программистов (и для knowlege workers в целом) самым продуктивным режимом работы является “поток” - полное погружение в одну задачу.
Длительная концентрация внимания на одной цели без переключений на другие.
Тем не менее, рано или поздно приходится делать перерыв и заниматься чем-то другим (хоть даже домашними делами), а к текущей задаче возвращаться спустя некоторое время.
С помощью дневника можно быстрее вспомнить, на чём мы остановились в прошлый раз, и куда надо двигаться дальше.
Экономится время и силы на возврат в поток.&lt;/p&gt;

&lt;p&gt;Также порой приходится обращаться к старым задачам спустя месяцы и годы, чтобы повторить их или хотя бы выяснить подробности сделанного.
Голова уже не сможет удержать всех нюансов задачи спустя такой промежуток времени.
А с записями ничего не случится.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Сохраняем маленькие полезняшки.&lt;/strong&gt;
Например, найденные в процессе работы ссылки.
Или полезные сниппеты кода.
Когда в следующий раз потребуется подобный кусок кода, не придётся восстанавливать его по памяти.
Можно будет просто найти в дневнике через поиск.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Формализуем непонятную проблему - так её легче решать.&lt;/strong&gt;
Многие знают так называемый &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D1%83%D1%82%D1%91%D0%BD%D0%BA%D0%B0&quot;&gt;“метод  утёнка”&lt;/a&gt;, который помогает решать непонятные вопросы.
Подробно рассказываешь свою проблему слушателю (в роли которого может выступать даже простая игрушка), и нередко получается так, что пока формулируешь проблему, мозг находит её решение.&lt;/p&gt;

&lt;p&gt;Такой подход замечательно работает и без слушателя!
Достаточно лишь взять и подробно &lt;strong&gt;расписать&lt;/strong&gt; свою проблему в дневнике, чем подробнее, тем лучше.
И это классно, особенно для интраверта: тихо что-то сам себе печатаешь и сам находишь решение.
Ни с кем разговаривать не нужно - красота!&lt;/p&gt;

&lt;blockquote&gt;

  &lt;p&gt;Решение сложной технической задачи нередко напоминает блуждание по лабиринту.
Двинулись в одну сторону - наткнулись на стену, пошли в другом направлении - встретили пропасть.
По ходу дела обнаружили сокровища в боковом туннеле, но сразу взять не получилось.
Рабочий дневник - это карта лабиринта, со всеми его поворотами, западнями и ништяками.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;недостатки-дневника-или-нет&quot;&gt;Недостатки дневника (или нет?)&lt;/h2&gt;

&lt;p&gt;Несложно увидеть и слабые стороны этого подхода.
Впрочем, не такие уж они и страшные!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Дополнительная трата времени.&lt;/strong&gt;
Да, ведение дневника отнимает достаточно много времени.
Не во всех случаях можно позволить себе такую роскошь.
Позволительно ли записывать свои планы в дневничок, когда нужно срочно &lt;em&gt;накатывать фикс на упавший прод&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;С другой стороны, авральное состояние - вещь достаточно противоестественная для интеллектуального работника.
В таком режиме сложно делать вещи качественно, очень возрастает риск где-нибудь ошибиться.
Ошибки приводят к новым авралам, а там недалеко и до сваливания в смертельный штопор!&lt;/p&gt;

&lt;p&gt;В такие моменты бывает лучше притормозить, немного подумать, чётко осознать причины проблемы - и сделать всё &lt;strong&gt;правильно.&lt;/strong&gt;
Не так уж редко оказывается, что первое пришедшее в голову решение правильным вовсе не является :)
Думаю, вы уже поняли, к чему я веду: замедляться при решении задач бывает не только не-вредно, но и полезно.
И дневник в этом только поможет!&lt;/p&gt;

&lt;blockquote&gt;

  &lt;p&gt;К слову, ничто не мешает описать сделанную работу в дневнике сразу после аврального фикса.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/images/worklogs/prof_fortran.png&quot; alt=&quot;prof&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;https://www.behance.net/gallery/35173039/Stickers-for-another-one-IT-conference-(DUMP2016)&quot;&gt;Alice Sleeping&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Дублирование работы.&lt;/strong&gt;
Уверен, у многих читателей к этому времени уже созрел такой вопрос: “&lt;em&gt;У нас ведь уже есть командный таск-трекер, разве его недостаточно для журналирования?&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;Однозначного ответа на этот вопрос у меня нет.
Обстоятельства у всех разные, и было бы неправильно подходить ко всем с одной меркой.
Могу сказать лишь за себя самого.
В дневник я позволяю себе записывать &lt;strong&gt;абсолютно что угодно.&lt;/strong&gt;
Самые мелкие напоминалки для себя, вопросы “эту штуку сделал, что делаем дальше?”, ключевую фразу “я туплю” и так далее.
Порой это десятки записей в день!
Если всё это писать в таск-трекер, коллеги взвоют из-за объёмов спама и навечно забанят меня :)&lt;/p&gt;

&lt;p&gt;Поэтому в трекер уходят только отфильтрованные записи.
Copy &amp;amp; paste, copy &amp;amp; paste…
Дублирование усилий получается, но не настолько уж большое.
Суммарные преимущества дневника всё равно перевешивают.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Лень вести дневник.&lt;/strong&gt;
Пожалуй, самый весомый и самый серьёзный недостаток.
Безусловно, на работу с дневником приходится тратить заметное количество сил и внимания.
Мозг сопротивляется этому как может.
Уж что-что, а экономить энергию он любит, хотя и не всегда делает это в правильных местах.&lt;/p&gt;

&lt;p&gt;Бывали и у меня периоды, когда я сдавался и временно отступал от ведения дневника.
Но всё же неизменно раз за разом возвращался к этой практике, слишком уж велики её преимущества!&lt;/p&gt;

&lt;p&gt;Как же сделать так, чтобы лень не вставала на пути?
Пара советов у меня найдётся.
Самое главное - стараться сделать ведение дневника максимально удобным и приятным занятием.
Использовать любимый текстовый редактор, сократить объём вспомогательных ручных действий, минимизировать неудобства и сфокусироваться на главном - фиксации важных моментов.&lt;/p&gt;

&lt;p&gt;Ещё неплохо помогает &lt;em&gt;формальный подход&lt;/em&gt;.
Максимальное сопротивление мозг оказывает тогда, когда записей по задаче ещё нет, но в голове уже куча идей, и хочется поскорее все их проверить в деле.
Важно в этот момент - в самом начале работы над задачей - применить волевое усилие и напомнить самому себе: “у меня есть &lt;strong&gt;правило&lt;/strong&gt;, что я веду дневник”.
Создать файл и записать в него все свои идеи.
Остальные записи уже пойдут гораздо легче.&lt;/p&gt;

&lt;h2 id=&quot;полезные-приёмы&quot;&gt;Полезные приёмы&lt;/h2&gt;

&lt;p&gt;Упомяну также несколько дополнительных хитростей, которые помогают в работе над дневником.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Выбираем формат дневника.&lt;/strong&gt;
Простого plain text не всегда бывает достаточно, когда мы работаем с кодом.
Сам я использую markdown и вполне им доволен: синтаксис поддерживается всеми популярными редакторами, удобно визуально выделять заголовки, код, цитаты и т.п.
Но остерегусь навязывать Markdown читателю.
У этого формата есть  альтернативу, например: Restructured Text, Wiki, &lt;a href=&quot;http://orgmode.org/&quot;&gt;Orgmode&lt;/a&gt;.
Главное, что требуется от формата - не вызывать раздражения при чтении и записи - ведь иначе дневник долго не проживёт!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Используем сниппеты.&lt;/strong&gt;
Некоторые операции приходится чаще других при работе с дневником.
Например, я привык начинать очередную запись с отметки времени, чтобы потом проще было ориентироваться.
Довольно очевидно, что вводить дату каждый раз руками слишком утомительно (порой десятки раз в день!).
Здесь стоит обратиться к возможностям текстового редактора, чтобы облегчить себе труд.
Я привык работать в Vim, а для него есть отличный плагин &lt;a href=&quot;http://www.vim.org/scripts/script.php?script_id=2540&quot;&gt;Snipmate&lt;/a&gt;.
Теперь мне достаточно написать слово time, нажать Tab - и отметка времени генерируется автоматически!
Аналогичные примочки есть, пожалуй, у каждого продвинутого текстового редактора.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Храним бэкапы.&lt;/strong&gt;
Чем дольше вы ведёте дневник, тем большую ценность начинает представлять сохранённая в нём информация.
Не лишним будет позаботиться о её сохранности!
Я настроил автокоммит в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt; и бэкап в bare-репозиторий внутри Dropbox по &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cron&lt;/code&gt;-у.
Теперь можно спать спокойно и не бояться, что дневник пропадёт вместе со сдохшим диском.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Расписываем контекст со всеми подробностями.&lt;/strong&gt;
Не очевидный, но очень существенный момент!
Во время работы над задачей много вещей кажутся очевидными, поэтому они могут не попадать в дневник.
Но вот проходит какое-то время, и весь контекст вытесняется из памяти.
Открываем дневник заново, видим какие-то обрывочные записи и ничего не можем вспомнить.
Восстановить удастся только то, что было записано.&lt;/p&gt;

&lt;p&gt;Представим, что мы работаем над веб-приложением, и натыкаемся в нём на ошибку.
Много ли будет пользы от дневника, который ведётся так?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Я что-то нажал, и всё сломалось :(((((&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Куда полезнее будет, если в запись попадёт и конкретная страница приложения, и проблемная кнопка на ней, и текст ошибки.&lt;/p&gt;

&lt;h2 id=&quot;в-заключение&quot;&gt;В заключение&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/worklogs/DaVinci_Crossbow.jpg&quot; alt=&quot;crossbow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%9B%D0%B5%D0%BE%D0%BD%D0%B0%D1%80%D0%B4%D0%BE_%D0%B4%D0%B0_%D0%92%D0%B8%D0%BD%D1%87%D0%B8#/media/File:DaVinci_Crossbow.JPG&quot;&gt;wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Я не претендую на лавры первооткрывателя этого метода.
&lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%9B%D0%B5%D0%BE%D0%BD%D0%B0%D1%80%D0%B4%D0%BE_%D0%B4%D0%B0_%D0%92%D0%B8%D0%BD%D1%87%D0%B8&quot;&gt;Прошаренные&lt;/a&gt; &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%AD%D0%B4%D0%B8%D1%81%D0%BE%D0%BD,_%D0%A2%D0%BE%D0%BC%D0%B0%D1%81_%D0%90%D0%BB%D0%B2%D0%B0&quot;&gt;товарищи&lt;/a&gt; с успехом &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D1%81%D0%BB%D0%B0,_%D0%9D%D0%B8%D0%BA%D0%BE%D0%BB%D0%B0&quot;&gt;используют&lt;/a&gt; его уже не одну сотню лет.&lt;/p&gt;

&lt;p&gt;Неожиданной может показаться разве что мысль, что рабочий дневник окажется полезен не только маститому изобретателю, но и скромному трудяге-программисту.
Но это так.
Дневник - один из инструментов, облегчающих путь к вершинам профессионального мастерства.
Каждый сам волен решать, применять ли его в своей работе.
И я буду рад, если моя статья хотя бы ненамного увеличит число тех, кто готов попытаться!&lt;/p&gt;

&lt;p&gt;Эта же статья &lt;a href=&quot;https://habrahabr.ru/post/324320/&quot;&gt;на хабре&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Автоматический поиск ошибок в Java-коде через генерацию тестов</title>
   <link href="/work/2015/12/10/find-errors-in-java-code-through-test-generation/"/>
   <updated>2015-12-10T00:00:00+00:00</updated>
   <id>/work/2015/12/10/find-errors-in-java-code-through-test-generation</id>
   <content type="html">&lt;p&gt;Почитал с утра &lt;a href=&quot;http://blog.acolyer.org/2015/11/16/simplifying-and-isolating-failure-inducing-input/&quot;&gt;пару&lt;/a&gt; &lt;a href=&quot;http://blog.acolyer.org/2015/11/17/hierarchical-delta-debugging/&quot;&gt;статей&lt;/a&gt; по delta debugging. Не могу теперь выкинуть из головы идею.&lt;/p&gt;

&lt;h2 id=&quot;простой-и-автоматический-поиск-ошибок&quot;&gt;Простой и автоматический поиск ошибок&lt;/h2&gt;

&lt;p&gt;Допустим, у нас есть большой унаследованный класс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GodlikeEnterpriseStaffController&lt;/code&gt;, который хочестя покрыть тестами. Но так как он большой и унаследованный, сделать это сложно, долго и трудно.&lt;/p&gt;

&lt;p&gt;Однако, представим, что у нас есть некая библиотека. Мы говорим ей “протестируй-ка нам класс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GodlikeEnterpriseStaffController&lt;/code&gt;, вот тебе ссылка на объект”. Та что-то шурудит у себя в кишках, а потом выдаёт примеры тест-кейсов типа:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Вызываем метод GodlikeEnterpriseStaffController.createFoo()
 * тот создаёт файл new File(...)
 * создание файла падает с исключением из-за отсутствия прав доступа
 * метод createFoo() падает с неожиданным IOException, хотя не должен
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Как бы такая библиотека работала под капотом?&lt;/p&gt;

&lt;h2 id=&quot;реализация&quot;&gt;Реализация&lt;/h2&gt;

&lt;p&gt;Используя Reflection, мы можем получить список методов класса, а также их параметры. Используя этот набор данных, библиотека может сгенерировать произвольную цепочку вызовов к данным методам, используя произвольные параметры. Если во время вызова с классом происходит &lt;em&gt;что-то непотребное&lt;/em&gt;, то данный набор вызовов обрабатывается с помощью delta debugging, чтобы превратиться в минимальный падающий тест-кейс. Который и показывается пользователю.&lt;/p&gt;

&lt;p&gt;Однако тут возникает вопрос - что делать с зависимостями? Как правило, классы не живут в изоляции (а наш &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GodlikeEnterpriseStaffController&lt;/code&gt; тем более). Что делать с различными обращениями к зависимостям?&lt;/p&gt;

&lt;p&gt;Ответ прост и брутален: все зависимости заворачиваются в моки. Для каждого метода мы снова можем определить через Reflection набор параметров и возвращаемые значения - и снова генерируем для них произвольные возвращаемые значения. В итоге, когда тестируемый класс делает вызовы других классов, то он получает полный ушат случайных значений в ответ. Либо, если в сигнатуре вызваемого метода прописана возможность выкинуть исключение, получает это исключение. Либо что-то ещё неприятное.&lt;/p&gt;

&lt;h2 id=&quot;профит&quot;&gt;Профит&lt;/h2&gt;

&lt;p&gt;Широко известно, что многим программистам сложно продумать все возможные пути исполнения их кода и то, как повлияют на поведение кода все возможные входные параметры. Автоматическая генерация подобных параметров помогла бы находить неожиданное поведение кода в полу-автоматическом режиме. Создаваемые системой “тест-кейсы” можно было бы либо превращать в реальные юнит-тесты, либо играть с генератором в пинг-понг (сквош?), на ходу исправляя в коде находимые компьютером проблемы.&lt;/p&gt;

&lt;p&gt;Более того, такой подход, на первый взгляд, намного привлекательней, чем обычный TDD или, тем более, property-driven testing. Программисту не нужно ни придумывать тест-кейсы, ни формулировать инварианты - просто брать и скармливать тест системе. Шикарно ведь, правда?&lt;/p&gt;

&lt;h2 id=&quot;почему-это-не-будет-работать&quot;&gt;Почему это не будет работать&lt;/h2&gt;

&lt;p&gt;В теории это всё хорошо, но на практике реализация подобной системы, скорее всего, столкнётся с рядом серьёзных вызовов.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Невозможность проверять бизнес-логику. Конечно, с помощью такой системы можно достаточно легко находить всякие &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NullPointerException&lt;/code&gt;, но такие находки могут оказаться максимумом, на что она способна.&lt;/li&gt;
  &lt;li&gt;Нереалистичные тест-кейсы. Созданные системой кейсы могут не отражать реальность, в которой выполняется код. Например, не учитывать, что одна из завимостей тестируемого класса является синглтоном (два разных обращения к зависимости создадут два разных мока). Или не учитывать &lt;em&gt;неявный контракт класса&lt;/em&gt;: в метод передаётся интерфейс, который внутри кастуется в конкретную реализацию этого интерфейса (да, это bad practice само по себе; однако без учёта подобной специфики мы упадём на первой строчке тестируемого метода с ClassCastException, а с учётом смогли бы пройти дальше). Как результат, все генерируемые тест-кейсы малополезны для реального применения.&lt;/li&gt;
  &lt;li&gt;Возможно, какие-то зависимости чисто технически сложно превращать в моки.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Конечно, несложно придумать способы борьбы с такими проблемами. Например, мы можем добавить &lt;em&gt;настройку окружения&lt;/em&gt;, в которой прописываем некоторые предположения относительно того, как следует генерировать варианты для зависимостей. Или указывать, что надо тестировать не один класс в изоляции, а некоторую группу классов (т.е., не заменять часть зависимостей на моки). Однако всё это компрометирует изначальную идею “просто скормил класс системе и получил результат”.&lt;/p&gt;

&lt;h2 id=&quot;итого&quot;&gt;Итого&lt;/h2&gt;

&lt;p&gt;Я подозреваю, что идея далеко не нова, и кто-то её уже давно высказывал. Возможно, даже какая-то из существующих систем работает подобным образом (какой-нибудь &lt;a href=&quot;http://www.quviq.com/products/quickcheck-for-c/&quot;&gt;Quviq QuickCheck&lt;/a&gt; или &lt;a href=&quot;https://github.com/DRMacIver/conjecture&quot;&gt;Conjecture&lt;/a&gt;; трудно об этом судить, не попробовав их в деле). Всё, что у меня пока что есть, - это мысленный эксперимент. Пускай пока в таком виде и остаётся.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Facebook Infer</title>
   <link href="/work/2015/06/18/facebook-infer/"/>
   <updated>2015-06-18T00:00:00+00:00</updated>
   <id>/work/2015/06/18/facebook-infer</id>
   <content type="html">&lt;p&gt;На днях Facebook выпустил &lt;a href=&quot;http://fbinfer.com/&quot;&gt;новую утилиту&lt;/a&gt; для статического анализа кода на Java/C.
Не смог пройти мимо, решил натравить его на &lt;a href=&quot;https://github.com/apatrushev/ServerAccess&quot;&gt;ServerAccess&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;В целом, делается это довольно тривиально: качаем, запускаем…&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;ahitrin@ahitrin:~/projects/ServerAccess&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./gradlew clean
:clean

BUILD SUCCESSFUL

Total &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;: 2.756 secs&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Если не сделать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clean&lt;/code&gt;, то ленивый Gradle ничего не будет компилировать, и Infer не найдёт никаких ошибок.
А после очистки сразу что-то нашёл!&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;ahitrin@ahitrin:~/projects/ServerAccess&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;~/opt/infer-linux64-v0.1.0/infer/infer/bin/infer &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; ./gradlew build
Starting analysis &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Infer version v0.1.0&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Analysis &lt;span class=&quot;k&quot;&gt;done

&lt;/span&gt;77 files analyzed


/home/ahitrin/projects/ServerAccess/src/main/java/ru/naumen/servacc/HTTPProxy.java:96: error: RESOURCE_LEAK
   resource acquired by call to accept&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; at line 95 is not released after line 96

/home/ahitrin/projects/ServerAccess/src/main/java/ru/naumen/servacc/HTTPProxy.java:99: error: RESOURCE_LEAK
   resource acquired by call to ServerSocket&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; at line 92 is not released after line 99

/home/ahitrin/projects/ServerAccess/src/main/java/ru/naumen/servacc/settings/PropertiesFile.java:56: error: RESOURCE_LEAK
   resource acquired by call to FileOutputStream&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; at line 52 is not released after line 56

/home/ahitrin/projects/ServerAccess/src/main/java/ru/naumen/servacc/settings/PropertiesFile.java:60: error: RESOURCE_LEAK
   resource acquired by call to FileOutputStream&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; at line 52 is not released after line 60

/home/ahitrin/projects/ServerAccess/src/test/java/ru/naumen/servacc/test/settings/FileUtils.java:34: error: RESOURCE_LEAK
   resource acquired by call to FileWriter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; at line 32 is not released after line 34

/home/ahitrin/projects/ServerAccess/src/test/java/ru/naumen/servacc/test/settings/FileUtils.java:46: error: RESOURCE_LEAK
   resource acquired by call to FileReader&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; at line 40 is not released after line 46&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Завёл &lt;a href=&quot;https://github.com/apatrushev/ServerAccess/issues/58&quot;&gt;баг&lt;/a&gt;, буду на досуге копаться.&lt;/p&gt;

&lt;p&gt;Что бы ещё теперь проверить?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UDP&lt;/strong&gt;: а вот ещё что обнаружилось…&lt;/p&gt;

&lt;p&gt;Оказывается, что, в отличие от других средств анализа (FindBugs, PMD,..) у infer есть интересный режим работы: показ целого трейса, где происходит утечка.
Это что-то новенькое!&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;ahitrin@ahitrin:~/projects/ServerAccess&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;~/opt/infer-linux64-v0.1.0/infer/infer/bin/inferTraceBugs &lt;span class=&quot;nt&quot;&gt;--select&lt;/span&gt; 4 &lt;span class=&quot;nt&quot;&gt;--max-level&lt;/span&gt; max
/home/ahitrin/projects/ServerAccess/src/test/java/ru/naumen/servacc/test/settings/FileUtils.java:34: error: RESOURCE_LEAK
   resource acquired by call to FileWriter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; at line 32 is not released after line 34

Showing all 5 steps of the trace

/home/ahitrin/projects/ServerAccess/src/test/java/ru/naumen/servacc/test/settings/FileUtils.java:29: start of procedure write&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;...&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
27   public class FileUtils
28   &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
29 &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;     public static void write&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;File file, String contents&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; throws IOException
30       &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
31           file.createNewFile&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

/home/ahitrin/projects/ServerAccess/src/test/java/ru/naumen/servacc/test/settings/FileUtils.java:31:
29       public static void write&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;File file, String contents&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; throws IOException
30       &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
31 &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;         file.createNewFile&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
32           PrintWriter writer &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; new PrintWriter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;new FileWriter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
33           writer.print&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;contents&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

/home/ahitrin/projects/ServerAccess/src/test/java/ru/naumen/servacc/test/settings/FileUtils.java:32:
30       &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
31           file.createNewFile&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
32 &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;         PrintWriter writer &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; new PrintWriter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;new FileWriter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
33           writer.print&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;contents&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
34           writer.flush&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

/home/ahitrin/projects/ServerAccess/src/test/java/ru/naumen/servacc/test/settings/FileUtils.java:33:
31           file.createNewFile&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
32           PrintWriter writer &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; new PrintWriter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;new FileWriter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
33 &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;         writer.print&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;contents&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
34           writer.flush&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
35       &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

/home/ahitrin/projects/ServerAccess/src/test/java/ru/naumen/servacc/test/settings/FileUtils.java:34:
32           PrintWriter writer &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; new PrintWriter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;new FileWriter&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
33           writer.print&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;contents&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
34 &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;         writer.flush&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
35       &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
36&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Безусловно, проект ещё сырой (на большом рабочем проекте он позорно помер), но приёмчики работы у него интересные.
Глядишь, что-то полезное и вырастет в итоге.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>О темпоральной логике</title>
   <link href="/work/2015/06/05/on-temporal-logic/"/>
   <updated>2015-06-05T00:00:00+00:00</updated>
   <id>/work/2015/06/05/on-temporal-logic</id>
   <content type="html">&lt;p&gt;Давеча позволил себе немного пожаловаться в твиттер:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; lang=&quot;en&quot;&gt;&lt;p lang=&quot;ru&quot; dir=&quot;ltr&quot;&gt;Человеческий мозг крайне плохо умеет работать с темпоральной логикой - вот причина многих ошибок в &lt;a href=&quot;https://twitter.com/hashtag/IT?src=hash&quot;&gt;#IT&lt;/a&gt;&lt;/p&gt;&amp;mdash; Andrey Hitrin (@ahitrin) &lt;a href=&quot;https://twitter.com/ahitrin/status/605735862450712576&quot;&gt;June 2, 2015&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; lang=&quot;en&quot;&gt;&lt;p lang=&quot;ru&quot; dir=&quot;ltr&quot;&gt;Особенно, если изменения затрагивают несколько систем&lt;/p&gt;&amp;mdash; Andrey Hitrin (@ahitrin) &lt;a href=&quot;https://twitter.com/ahitrin/status/605736117296611328&quot;&gt;June 2, 2015&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Небольшое &lt;em&gt;наблюдение&lt;/em&gt; за окружающей средой: возможно, мы (разработчики) недооцениваем, насколько часто мы ошибаемся при разработке взаимодействия распределённых систем.&lt;/p&gt;

&lt;p&gt;Пускай у разработчика есть система А и система B.
Они каким-то образом друг с другом взаимодействуют.
И в один прекрасный момент возникает необходимость что-то поменять в одной из этих систем (пускай, A → A’).&lt;/p&gt;

&lt;p&gt;Окей, говорит себе разработчик, тыжпрограммист!
Система A меняется, значит, надо поменять и систему B, чтобы она знала про эти изменения.
Берёт и адаптирует систему B к новым изменениям (B → B’), и думает, что молодец.&lt;/p&gt;

&lt;p&gt;А затем, в продакшене, оказывается, что системы могут обновляться по отдельности, и наша система A’ встаёт рядом с необновлённой B.
Или, наоборот, B’ пытается ужиться со старой A.&lt;/p&gt;

&lt;p&gt;И никто за разработчика не подумал, что же будет в таком случае.
А может быть что угодно.
Потому что новый способ работы далеко не всегда бывает совместим со старым.
Не совпадают протоколы, меняются опции вызова, формат хранения данных…&lt;/p&gt;

&lt;p&gt;И это, разумеется, приводит к появлению самых разнообразных отказов и фейлов, про которые никто не подумал заранее.&lt;/p&gt;

&lt;p&gt;Мораль у сказа до тупости простая: разработка ПО - это не та область деятельности, где годится применять экономию мышления, столь любимую нашим ленивым мозгом.
На любое изменение полезно смотреть не только в статике (“как есть сейчас” vs. “как должно быть”), но и в динамике (“как пройти из A в A’ и ничего не сломать по дороге”).
Можно называть этот подход хоть “темпоральной логикой” (по определению: учёт причинно-следственных связей в условиях времени), хоть системным анализом, хоть continuous delivery, хоть горшком - но только &lt;s&gt;в печку не ставить&lt;/s&gt; не забывать.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>MindMapping в графовых базах данных</title>
   <link href="/misc/2014/06/01/graph-db-mindmapping/"/>
   <updated>2014-06-01T00:00:00+00:00</updated>
   <id>/misc/2014/06/01/graph-db-mindmapping</id>
   <content type="html">&lt;p&gt;В голове уже очень долго созревала одна идея, и вчера она наконец-то материализовалась в своего рода озарение.&lt;/p&gt;

&lt;p&gt;Наверняка многие знакомы с концепцией Mind Mapping’а (не перевожу, потому что устоявшийся перевод мне не по душе).
Я тоже давно и с удовольствием использую эту методику, когда мне нужно проанализировать сложный вопрос.&lt;/p&gt;

&lt;p&gt;Однако существующие инструменты (например, &lt;a href=&quot;http://freemind.sourceforge.net/wiki/index.php/Main_Page&quot;&gt;Freemind&lt;/a&gt;, &lt;a href=&quot;http://www.xmind.net/&quot;&gt;XMind&lt;/a&gt;, &lt;a href=&quot;http://www.mindmup.com/&quot;&gt;mindmup&lt;/a&gt;), хоть и хороши, оставляют желать лучшего в некоторых случаях.
Например, порой хочется довольно странных вещей: вводить разные категории для узлов, разные категории для связей, фильтровать их по различным условиям, добавлять развёрнутые описания и т.п.
Существующие инструменты этого, как правило, не позволяют (либо позволяют, но не дают желаемой гибкости и удобства).&lt;/p&gt;

&lt;p&gt;Но стойте, у нас же есть &lt;em&gt;целый класс&lt;/em&gt; инструментов, которые предназначены именно для того, чтобы хранить самые разнообразные графы любых форм и цветов!
Имя им - “графовые базы данных”.
Что будет, если мы возьмём такую базу данных и начнём строить наш mind map в ней?
Вот я вчера и решил это попробовать.&lt;/p&gt;

&lt;p&gt;Чтобы не ходить далеко, взял первую же БД, про которую вспомнил: &lt;a href=&quot;http://www.neo4j.org/&quot;&gt;Neo4J&lt;/a&gt;.
В качестве рабочего примера решил расписать схему проблем, с которыми я сталкиваюсь при работе над &lt;a href=&quot;https://github.com/apatrushev/ServerAccess&quot;&gt;ServerAccess&lt;/a&gt;.
В этом проекте большое количество унаследованного, тесно связанного кода, и при добавлении новых фич значительную часть времени приходится тратить просто на то, чтобы не превратить код в свалку костылей.
Есть хороший метод (&lt;a href=&quot;http://mikadomethod.org/&quot;&gt;Mikado Method&lt;/a&gt;), позволяющий разбирать такие кучи, но он требует аккуратного составления карты проблем.
Так что, я решил убить сразу несколько зайцев: и опробовать Neo4J в деле, и составить схему проблем SA.&lt;/p&gt;

&lt;p&gt;Пока что дело движется совсем потихоньку.
Изучаю язык запросов &lt;a href=&quot;http://www.neo4j.org/learn/cypher&quot;&gt;Cypher&lt;/a&gt;, вырабатываю в голове правила для моей схемы, делаю первые наброски базы.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/graph-db-mindmapping/scheme.jpg&quot; alt=&quot;pic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Однако, некоторые интересные выводы можно сделать уже сейчас.&lt;/p&gt;

&lt;p&gt;Первое чёткое впечатление: скорость работы пользователя через СУБД &lt;strong&gt;заметно ниже&lt;/strong&gt;, чем в специализированных инструментах для MindMapping’а.
Это вызвано тем, что для заведения узла требуется набить руками кусок кода приличного размера, а не просто лупцануть по Enter.
Думаю, даже когда у меня будет больше опыта в Cypher, скорость работы вряд ли достигнет сравнимых показателей.
Поэтому для простых и &lt;em&gt;быстрых&lt;/em&gt; карт графовая БД однозначно не подходит, и следует без колебаний выбирать специализированный инструмент.&lt;/p&gt;

&lt;p&gt;Тем не менее, возможности БД по работе с графами внушают оптимизм.
Чувствуется, что база данных не ограничивает тебя, а, наоборот, позволяет настроить себя под твои нужды максимально точным образом.
Можно ввести произвольные “типы” для узлов (в Neo4J это называется Labels), добавлять им произвольные свойства, создавать между ними связи разных типов (и тоже со своими свойствами), превращать свойства в новые узлы, превращать узлы в свойства, фильтровать сущности произвольным образом…&lt;/p&gt;

&lt;p&gt;Веб-интерфейс Neo4J - это вообще шедевр, который заслуживает отдельной похвалы.
Пожалуй, стоило поставить себе эту СУБД, чтобы хотя бы поглядеть, как следует работать с современным вебом.
Совершенно не ожидал такого изящества от “обычной базы данных”.&lt;/p&gt;

&lt;p&gt;Впрочем, посмотрим, как дело пойдёт дальше.
Справится ли Neo4J с моими потребностями, и справлюсь ли я сам с Neo4J.
Будет ли удобно поддерживать и развивать базу данных по мере её роста.
Хватит в работе ли веб-морды, или потребуется создавать собственные дополнительные инструменты.
Поживём - увидим!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Braindump</title>
   <link href="/misc/2014/03/16/braindump/"/>
   <updated>2014-03-16T00:00:00+00:00</updated>
   <id>/misc/2014/03/16/braindump</id>
   <content type="html">&lt;p&gt;Давайте уж не стану ничего писать про сам GTD, все эти вещи гуглятся на раз.
Просто расскажу один маленький эпизод из собственного опыта освоения этой методики.&lt;/p&gt;

&lt;p&gt;Со всей GTD-шной организацией задач у меня давно была одна неувязка: &lt;strong&gt;куда и как записывать&lt;/strong&gt;?
Вот сижу я за компом, и надо по-быстрому избавиться от мысли в голове.
И каждый раз это целая проблема.
Проблема выбора.&lt;/p&gt;

&lt;p&gt;Записать её куда-то на локальный диск?
А как потом найти то, что записал?
Как найти эту запись на другом компьютере?&lt;/p&gt;

&lt;p&gt;Записать в веб-сервис?
Какой?
Открывать браузер и открывать какую-то страницу?
Долго и неудобно.&lt;/p&gt;

&lt;p&gt;Куча вопросов, и все отвлекают от главной задачи: сбросить текущие мысли из головы.
Ведь это всё, что на самом деле нужно в данный момент.&lt;/p&gt;

&lt;p&gt;Каждый решает эту проблему по-своему, и я тоже, в конце-концов, нашёл рабочий вариант.
Всё делается сурово и по-программистски.&lt;/p&gt;

&lt;h1 id=&quot;брэйндамп&quot;&gt;Брэйндамп!&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Шаг первый&lt;/strong&gt;: пишем труъ-скрипт:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% cat ~/bin/braindump
#!/bin/bash
now=`date +&quot;%Y-%m-%d-%H-%M&quot;`
cd ~/opt/Dropbox/braindump/
find . -name \*.md -size 0 -delete
gvim -c &quot;set spell&quot; -c &quot;call system(&apos;wmctrl -i -b add,fullscreen -r&apos;.v:windowid)&quot; ${now}.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Что он делает:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;запускает GVim в полноэкранном режиме и с проверкой правописания&lt;/li&gt;
  &lt;li&gt;настраивает редактор на запись в Markdown-файл в специальной папке&lt;/li&gt;
  &lt;li&gt;а перед этим чистит все пустые файлы в этой папке&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Шаг два&lt;/strong&gt;: создаём desktop-файл для запуска этого скрипта:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% cat ~/.local/share/applications/braindump.desktop
#!/usr/bin/env xdg-open
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Exec=~/bin/braindump
Name=Braindump
Icon=/usr/share/pixmaps/vim.svg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Шаг три&lt;/strong&gt;: используем &lt;a href=&quot;https://apps.ubuntu.com/cat/applications/synapse/&quot;&gt;Synapse&lt;/a&gt; для реактивного запуска новоиспечённого инструмента braindump.&lt;/p&gt;

&lt;p&gt;Конечно, Synapse не идеален, но для меня он стал самым удобным и быстрым способам запускать любые десктопные программы.
Пальцы легко запоминают нужные комбинации клавиш и запускают любую программу за доли секунды.
В том числе и мой Braindump.&lt;/p&gt;

&lt;h1 id=&quot;что-имеем-в-итоге&quot;&gt;Что имеем в итоге&lt;/h1&gt;

&lt;p&gt;Самое главное - это выработать в себе привычку разбирать записи.
После этого Braindump начинает работать как часы.&lt;/p&gt;

&lt;p&gt;Возникла мысль, которую надо &lt;em&gt;срочно записать&lt;/em&gt;?
Быстрая пробежка по клавиатуре - и практически сразу открывается окно, в котором можно начинать писать.
Не надо думать о том, где сохраняется запись - просто записал её и вышел из редактора.
Не надо думать о синхронизации записей - обо всём позаботится Dropbox.
В полноэкранном режиме ничего не отвлекает от мысли.
Работает привычное Markdown-форматирование - можно сделать всё красиво.&lt;/p&gt;

&lt;p&gt;Появилось &lt;em&gt;свободное время&lt;/em&gt;, которое можно потратить на разбор задач?
Снова открываем Braindump, а в нём - это же обычный Vim! - список сохранённых файлов (я использую для этого удобнейший плагин &lt;a href=&quot;http://www.vim.org/scripts/script.php?script_id=1658&quot;&gt;NerdTree&lt;/a&gt;).
Разбираемся со всеми записями (чаще всего у меня они уходят в Trello) и просто удаляем их из файла.
Сам файл нет нужды трогать - он уйдёт при следующем запуске Braindump.
Даже если я ещё не придумал, куда девать очередную запись, она может некоторое время пожить.
Но в целом стараюсь не запускать ситуацию.
Нет ничего лучше пустого inbox-а.&lt;/p&gt;

&lt;p&gt;После изобретения этого велосипеда жизнь моя стала легче (а волосы мягче и шелковистей).
Приятно, что удалось обойтись существующими и привычными инструментами, а не городить что-то новое.
По сути, я не решил проблему выбора “куда поместить идею из головы”, а всего лишь отделил её от сохранения самой идеи.
Оно и правильно.&lt;/p&gt;

&lt;p&gt;Всем прочитавшим хорошего дня и шикарных идей (которые никуда не теряются)!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Новогодний шоппинг</title>
   <link href="/misc/2014/01/05/New-Year-Shopping/"/>
   <updated>2014-01-05T00:00:00+00:00</updated>
   <id>/misc/2014/01/05/New-Year-Shopping</id>
   <content type="html">&lt;p&gt;Перед Новым Годом удалось закупиться некоторым количеством книжек. Для удобства обзора разбил их по категориям.&lt;/p&gt;

&lt;h2 id=&quot;управление-проектами&quot;&gt;Управление проектами&lt;/h2&gt;

&lt;p&gt;Продолжаю грызть гранит науки “как бы работать поменьше, да получить побольше”. Другими словами, как отличать полезную деятельность от бесполезной, чтобы тратить время только на первую.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/1449305172/&quot;&gt;Running Lean&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/1449335675/&quot;&gt;Lean Analytics&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/0988262592/&quot;&gt;The Phoenix Project&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ozon.ru/context/detail/id/4784265/&quot;&gt;Вовремя и в рамках бюджета&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ozon.ru/context/detail/id/4119528/&quot;&gt;Статистическое управление процессами&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ozon.ru/context/detail/id/5288956/&quot;&gt;Теория ограничений Голдратта&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ozon.ru/context/detail/id/5572374/&quot;&gt;Я так и знал!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;психология&quot;&gt;Психология&lt;/h2&gt;

&lt;p&gt;Крайне полезная категория. Управление проектом под названием “я сам”.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/0321704452/&quot;&gt;The Naked Presenter&lt;/a&gt;, чтобы эффективнее выступать с презентациями.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/0932633161/&quot;&gt;Are Your Lights On?&lt;/a&gt;, чтобы мозги работали как надо.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ozon.ru/context/detail/id/5702158/&quot;&gt;Ментальные ловушки&lt;/a&gt;, чтобы не попадаться в эти ловушки.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;прочее&quot;&gt;Прочее&lt;/h2&gt;

&lt;p&gt;Не удержался и на всякий случай взял ещё одну книгу про автоматическое тестирование. Даже если не найду ничего нового для себя, оставлю её в библиотеке отдела. А то там давненько не появлялось новинок.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ozon.ru/context/detail/id/19383906/&quot;&gt;ATDD. Разработка программного обеспечения через приемочные тесты&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;благодарности&quot;&gt;Благодарности&lt;/h2&gt;

&lt;p&gt;Мне повезло: все эти книги удалось купить, не потратив ни рубля собственных денег. Все расходы на них оплатила &lt;a href=&quot;http://www.naumen.ru&quot;&gt;родная компания&lt;/a&gt;. С другой стороны, это привело к тому, что я совершал выбор не слишком тщательно. И некоторые книги пересекаются друг с другом более плотно, чем хотелось бы. Впрочем, это не слишком страшно. Можно просто оставить компании несколько из них. Пускай все читают.&lt;/p&gt;

&lt;p&gt;При выборе книг по Lean я ориентировался на &lt;a href=&quot;http://www.noop.nl/2013/08/top-100-agile-books-edition-2013.html&quot;&gt;вот этот список&lt;/a&gt; от месье &lt;a href=&quot;https://twitter.com/jurgenappelo&quot;&gt;Юргена Аппело&lt;/a&gt;. За что ему хочется послать огромное виртуальное “спасибо”.&lt;/p&gt;

&lt;p&gt;И спасибо &lt;a href=&quot;https://twitter.com/yantonov&quot;&gt;Юре Антонову&lt;/a&gt;, который попросил меня рассказать о покупках. Без него этого поста и не было бы!&lt;/p&gt;

&lt;p&gt;Осталось только найти время, чтобы все эти книжечки скушать. Впрочем, рецепт добычи времени известен: не тратить его на всевозможную непродуктивную деятельность, и все будет пучком. Посмотрим, как будет это получаться.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>The Joy Of Fireplace</title>
   <link href="/work/2013/12/19/The-joy-of-Fireplace/"/>
   <updated>2013-12-19T00:00:00+00:00</updated>
   <id>/work/2013/12/19/The-joy-of-Fireplace</id>
   <content type="html">&lt;p&gt;Я давно присматривался к замечательному языку Clojure, но никак не мог начать строить с ним хорошие отношения.
Проблема банальная: никак не мог найти удобную среду разработки, в которой чувствовал бы себя комфортно и понимал, что происходит.
Исторически, самая лучшая поддержка LISP-языков “из коробки” встроена в великий и ужасный Emacs.
Но с ним не дружат мои пальцы.&lt;/p&gt;

&lt;p&gt;Первые попытки подружить Clojure и мой любимый Vim я осуществлял ещё пару лет назад, но все они потерпели крах.
Выбор был невелик.
Либо скрепя сердце переходить на пальцеломный Emacs, либо заниматься бесконечной обезъяньей копипастой из редактора в REPL и обратно.
Либо пинать страшный, тяжеловесный, мышевозный, недоделанный LightTable.&lt;/p&gt;

&lt;p&gt;Но страдания наконец-то закончились, раз и навсегда!&lt;/p&gt;

&lt;p&gt;Теперь, чтобы эффективно работать на Clojure в Vim, достаточно знать всего два слова: &lt;a href=&quot;http://leiningen.org/&quot;&gt;Lein&lt;/a&gt; и &lt;a href=&quot;http://leiningen.org/&quot;&gt;Fireplace&lt;/a&gt;.
Первый и так является стандартом де-факто в Clojure-разработке (насколько мне известно).
А второй, видимо, станет любимой игрушкой кложуристов-вимеров.
Плагин подкупает удобством и лёгкостью освоения.
Достаточно просто запустить &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lein repl&lt;/code&gt;, зайти vim-ом в папку проекта и открыть любой &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.clj&lt;/code&gt;-файл.
После этого любой его кусок можно отправлять в REPL, вычислять любые выражения, обрабатывать код почти со скоростью мысли.
Вырастать из штанишек упражнений и начать создавать &lt;a href=&quot;https://github.com/ahitrin/clokado&quot;&gt;свои маленькие проекты&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Самое главное: &lt;strong&gt;использовать Lein версии не ниже 2.0&lt;/strong&gt;.
Т.е., если в репозитории используется более старая его версия (привет, Ubuntu!), просто используйте инсталлятор с официального сайта.&lt;/p&gt;

&lt;p&gt;Пишите на Clojure и радуйтесь!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Trello Checklists</title>
   <link href="/work/2013/07/09/trello-checklists/"/>
   <updated>2013-07-09T00:00:00+00:00</updated>
   <id>/work/2013/07/09/trello-checklists</id>
   <content type="html">&lt;p&gt;&lt;img src=&quot;/images/trello-checklist.png&quot; alt=&quot;pic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://trello.com&quot;&gt;Trello&lt;/a&gt; - бесплатный и очень удобный инструмент для управления задачами. Я раскидываю в нём свои задачи, рабочие и личные. А недавно нашёл ещё одно применение.&lt;/p&gt;

&lt;p&gt;При запуске очередной итерации в скраме требуется выполнять целый ряд бюрократических операций. Чтобы ничего не забывать, я завёл отдельный чеклист, в котором перечислил все задачи. Теперь этот процесс стал куда менее напряжным (хотя и по-прежнему занимает довольно много времени). Не нужно каждый раз напрягать память и вспоминать, всё ли ты сделал. Более того, сейчас я могу легко тюнить процесс: менять очерёдность шагов, добавлять новые, убирать ненужные.&lt;/p&gt;

&lt;p&gt;Даже немного удивительно, что решение было под носом, а искать его пришлось несколько месяцев.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BTW&lt;/strong&gt;, кто-нибудь может порекомендовать хорошие практики груминга задач? Есть ощущение, что сейчас я его провожу довольно по-тупому. Просто просматриваю быстренько весь список и выкидываю, что мне не нравится.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Jenkins Copy Artifact Plugin</title>
   <link href="/work/2013/06/22/jenkins-copy-artifact-plugin/"/>
   <updated>2013-06-22T00:00:00+00:00</updated>
   <id>/work/2013/06/22/jenkins-copy-artifact-plugin</id>
   <content type="html">&lt;p&gt;Допустим, у вас есть несколько задач в Jenkins, и между ними надо передавать параметры. Например, номер версии.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/virtwol&quot;&gt;Макс Захаров&lt;/a&gt; в своё время зарекомендовал мне плагин под названием &lt;a href=&quot;http://wiki.jenkins-ci.org/display/JENKINS/Parameterized+Trigger+Plugin&quot;&gt;Jenkins Parameterized Trigger plugin&lt;/a&gt;. Вот его подробная &lt;a href=&quot;http://habrahabr.ru/post/168451/&quot;&gt;статья&lt;/a&gt; про автоматизацию тестирования, кстати.&lt;/p&gt;

&lt;p&gt;Но есть более удобный способ делать это. Просто используйте &lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/Copy+Artifact+Plugin&quot;&gt;Copy Artifacts Plugin&lt;/a&gt;. Первая задача может создавать небольшой текстовый файл, в который записывается номер версии. В конце сборки заархивируйте этот файл. А в следующие задачи добавьте шаг Copy artifacts from another project, чтобы они забирали этот файл.&lt;/p&gt;

&lt;p&gt;Плюсы этого подхода:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Это естественный для jenkins способ связывать задачи друг с другом. Если вы хотите отслеживать взаимосвязь, то всё равно придётся передавать какие-то файлы&lt;/li&gt;
  &lt;li&gt;Это удобнее, чем текстовый параметр. Опечататься невозможно.&lt;/li&gt;
  &lt;li&gt;При ручном запуске задачи будет нужно жать на кнопку всего один раз :)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Важные нюансы:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Не забывайте про архивирование. Чтобы файл можно было использовать в других задачах, его предварительно надо заархивировать.&lt;/li&gt;
  &lt;li&gt;Будьте внимательны к правилам, которые используются у вас для удаления старых сборок. Если есть риск того, что сборка головной задачи может исчезнуть до того, как запустится самая последняя задача в цепочке, то более безопасно будет копировать артефакт с одной задачи на другую (и архивировать на каждой). Это получается не так изящно, зато более надёжно.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Eclipse 4.3 Changelog</title>
   <link href="/work/2013/02/13/Eclipse-4.3-Changelog/"/>
   <updated>2013-02-13T00:00:00+00:00</updated>
   <id>/work/2013/02/13/Eclipse-4.3-Changelog</id>
   <content type="html">&lt;p&gt;Из твиттера пришла очень весёлая &lt;a href=&quot;http://leonsbuddydave.tumblr.com/post/42394969682/eclipse-4-3-changelog-added-the-word-eclipse&quot;&gt;ссылка&lt;/a&gt;, вот я и решил сделать перевод на скорую руку.&lt;/p&gt;

&lt;p&gt;Приятно чувствовать что я далеко не одинок в нелюбви к Eclipse.&lt;/p&gt;

&lt;p&gt;К сожалению, пару моментов в конце недопонял.&lt;/p&gt;

&lt;h2 id=&quot;eclipse-43-changelog&quot;&gt;Eclipse 4.3 changelog&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;В словарь добавлено слово “Eclipse”&lt;/li&gt;
  &lt;li&gt;Исправлена ситуация, в которой Eclipse не мог беспричинно упасть&lt;/li&gt;
  &lt;li&gt;Усилена насыщенность иконки приложения&lt;/li&gt;
  &lt;li&gt;Все пункты меню распечатаны на бумаге, бумага разрезана на полоски, полоски развеяны по ветру. Меню Eclipse изменено в соответствии с новой конфигурацией&lt;/li&gt;
  &lt;li&gt;Добавлена шестисекундная задержка к автодополнению кода&lt;/li&gt;
  &lt;li&gt;Добавлена восьмисекундная задержка к реакции на клавиатуру&lt;/li&gt;
  &lt;li&gt;Изменён размер изображения на сплэш-скрине&lt;/li&gt;
  &lt;li&gt;Добавлена десятисекундная задержка к сплэш-скрину&lt;/li&gt;
  &lt;li&gt;Вызовы &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc(&lt;/code&gt; переопределены на &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc(2*&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Исправлена необычная ситуация, когда окно редактора кода отзывается на действия пользователя&lt;/li&gt;
  &lt;li&gt;Удалён пункт меню “Проверить обновления”; его функциональность перемещена в отдельную гигантскую тяжеловесную панель, потому что не пойти бы вам…&lt;/li&gt;
  &lt;li&gt;Исправлена ситуация, в которой парсер XML мог обнаруживать ошибки, не завесив при этом Windows&lt;/li&gt;
  &lt;li&gt;Удалены и исправлены несколько интуитивных, юзабельных диалоговых окон&lt;/li&gt;
  &lt;li&gt;Начато использование системы контроля версий&lt;/li&gt;
  &lt;li&gt;Почуствовал себя достаточно хорошо, чтобы закрыть ящик с оружием на целых десять минут (?) - &lt;em&gt;Felt good enough to lock the gun drawer for ten whole minutes&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;s&gt;Переписали всё на C/С++&lt;/s&gt;
  &lt;/li&gt;
  &lt;li&gt;(Неудачно) попытался пересечь церковную ограду (?) - &lt;em&gt;Triead and failed to cross the threshold of church&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you enjoy it too.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Три Новых Консольных Утилиты Для Счастья</title>
   <link href="/work/2012/12/15/%D0%A2%D1%80%D0%B8-%D0%BD%D0%BE%D0%B2%D1%8B%D1%85-%D0%BA%D0%BE%D0%BD%D1%81%D0%BE%D0%BB%D1%8C%D0%BD%D1%8B%D1%85-%D1%83%D1%82%D0%B8%D0%BB%D0%B8%D1%82%D1%8B-%D0%B4%D0%BB%D1%8F-%D1%81%D1%87%D0%B0%D1%81%D1%82%D1%8C%D1%8F/"/>
   <updated>2012-12-15T00:00:00+00:00</updated>
   <id>/work/2012/12/15/Три-новых-консольных-утилиты-для-счастья</id>
   <content type="html">&lt;p&gt;В последнее время я добавил в свой арсенал консольных утилит три новые штучки, которые приносят заметную пользу. Захотелось написать про них пару слов.&lt;/p&gt;

&lt;p&gt;Всё написанное ниже стоит рассматривать через призму “я линуксоид, который уйму времени проводит в консоли”. Если для Вас это не верно, то мой опыт вряд ли окажется полезным.&lt;/p&gt;

&lt;h2 id=&quot;tmux&quot;&gt;Tmux&lt;/h2&gt;

&lt;p&gt;Первым на сцену выходит &lt;a href=&quot;http://tmux.sourceforge.net/&quot;&gt;terminal multiplexer&lt;/a&gt;. Я давно уже слышал о великом противостоянии Screen vs. Tmux, и всё хотел попробовать их в деле и определиться со своей стороной. Когда же наконец решился, выбор оказался до безумного прост.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p&gt;Всего два дня на &lt;a href=&quot;https://twitter.com/search/%23tmux&quot;&gt;#tmux&lt;/a&gt;, и я уже не понимаю, как жил без него раньше ;(&lt;/p&gt;&amp;mdash; Andrey Hitrin (@ahitrin) &lt;a href=&quot;https://twitter.com/ahitrin/status/270866557855469568&quot; data-datetime=&quot;2012-11-20T12:29:54+00:00&quot;&gt;November 20, 2012&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Оказалось, что хоткеи tmux запоминаются очень быстро и легко. По удобству использования он влёгкую обделывает обычный терминал. Кроме того, порой дико выручает сохранение сессии - фича, знакомая всем пользователям подобного софта.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p&gt;Теперь есть табы даже в банальном xterm, и, что важнее, рабочая сессия доступна и с планшета, и с ноута из дома - был бы только ssh-доступ&lt;/p&gt;&amp;mdash; Andrey Hitrin (@ahitrin) &lt;a href=&quot;https://twitter.com/ahitrin/status/270867410188394496&quot; data-datetime=&quot;2012-11-20T12:33:17+00:00&quot;&gt;November 20, 2012&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Но ещё более приятным оказалось возможность открывать несколько терминалов в одном табе. Если они связаны &lt;em&gt;единой задачей&lt;/em&gt;, то такой способ размещения оказывается наиболее естественным и удобным. Вся необходимая информация оказывается на одном экране. В каком-нибудь gnome terminal в этом случае всё равно пришлось бы скакать между разными вкладками (и ещё всё время вспоминать, на какую конкретно надо перейти).&lt;/p&gt;

&lt;p&gt;Не обошлось и без маленькой ложки дёгтя. Мои пальцы теперь стали иногда вызывать хоткеи tmux в vim. Неприятный сайд-эффект, но, думаю, я с ним разберусь.&lt;/p&gt;

&lt;h2 id=&quot;t&quot;&gt;t&lt;/h2&gt;

&lt;p&gt;Я использую несколько разных таск-трекеров, каждый для своей цели. Официальный рабочий инструмент - это Redmine, главный личный инструмент - это Google Tasks. Но они оказываются слишком громоздкими, когда ты разбил большую задачу на много мелких частей и пытаешься быстро добавлять и удалять их в ходе работы.&lt;/p&gt;

&lt;p&gt;Покопавшись в памяти, я внезапно нашёл решение и опробовал его в деле.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p&gt;t &lt;a href=&quot;http://t.co/kU3P11da&quot; title=&quot;http://stevelosh.com/projects/t/&quot;&gt;stevelosh.com/projects/t/&lt;/a&gt; отлично подходит для управления кучей мелких текущих задач, которые нет смысла трекать в серьёзных менеджерах&lt;/p&gt;&amp;mdash; Andrey Hitrin (@ahitrin) &lt;a href=&quot;https://twitter.com/ahitrin/status/274387966267387905&quot; data-datetime=&quot;2012-11-30T05:42:43+00:00&quot;&gt;November 30, 2012&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Всё очень просто и удобно. Пришла мысль в голову - тут же записал её, не отрываясь от консоли: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t какая-то задача&lt;/code&gt;. Самое крутое в том, что не надо ничего дополнительно открывать, ничего дополнительно запускать, не надо думать, &lt;em&gt;куда&lt;/em&gt; отправится задача. Ты её просто записал, и она просто сохранилась.&lt;/p&gt;

&lt;p&gt;Затем смотрим список всех задач, выбираем ровно одну и доводим её до конца. Вычёркиваем из списка и выбираем новую. В процессе работы возникает важная мысль - просто кидаем её в t. Разберёмся потом. В общем, стандартный GTD-процесс :)&lt;/p&gt;

&lt;p&gt;Одной из фич t является то, что список задач никак не упорядочен. Честно говоря, &lt;em&gt;иногда&lt;/em&gt; хочется чего-то более упорядоченного. Например, во время рефакторинга кода с применением &lt;a href=&quot;http://mikadomethod.org/&quot;&gt;Mikado Method&lt;/a&gt;, когда все цели зависят друг от друга. Было бы круто найти (или, на крайний случай, сделать) что-то подобное для mikado. Будет удобно.&lt;/p&gt;

&lt;h2 id=&quot;ssh-agent&quot;&gt;ssh-agent&lt;/h2&gt;

&lt;p&gt;На эту тему никаких твитов не было :)&lt;/p&gt;

&lt;p&gt;Всё довольно тривиально. Если Вы часто работаете с ssh, то знаете, что он часто просит пароль. Если использовать ключи, то они тоже просят пароль. При частом общении с ssh (например, во время активной синхронизации с внешними серверами в git) это быстро становится довольно утомительным. ssh-agent - это один из механизмов, которые позволяют ввести пароль лишь однажды за сессию, а затем поручить этот процесс машине.&lt;/p&gt;

&lt;p&gt;Конечно, при работе под Gnome или KDE есть свои графические аналоги ssh-agent, которые решают ту же задачу. Но ведь на них не заканчивается список оконных менеджеров, верно? На рабочем компьютере я сейчас использую Openbox, потому что он работает быстрее и почти не сношает мозг, в отличие от. Поэтому ssh-agent пришёлся мне вполне ко двору.&lt;/p&gt;

&lt;p&gt;В общем, просто оставлю здесь &lt;a href=&quot;http://xgu.ru/wiki/%D0%A3%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BB%D1%8E%D1%87%D0%B0%D0%BC%D0%B8_SSH_%D1%81_%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E_%D0%B0%D0%B3%D0%B5%D0%BD%D1%82%D0%B0&quot;&gt;мануал&lt;/a&gt; по этой полезной утилите.&lt;/p&gt;

&lt;h2 id=&quot;послесловие&quot;&gt;Послесловие&lt;/h2&gt;

&lt;p&gt;Описывая все эти инструменты, я чувствовал себя в некотором роде слоупоком. В самом деле, все они уже достаточно давно существуют и довольно широко известны (за исключением t, пожалуй). С другой стороны, для кого-то другого эта информация может оказаться новой и (скромно надеюсь) полезной. На пути к совершенству много шагов, и каждый из них важен. Я стал чуть более продуктивен с tmux, чуть более организован с t и испытываю меньше боли в пальцах с ssh-agent. Надеюсь, эта информация о них послужат кому-то ещё.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Как Добавить Ссылку На Трекер В Robot Framework</title>
   <link href="/work/2012/09/27/%D0%9A%D0%B0%D0%BA-%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C-%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D1%83-%D0%BD%D0%B0-%D1%82%D1%80%D0%B5%D0%BA%D0%B5%D1%80-%D0%B2-Robot-Framework/"/>
   <updated>2012-09-27T00:00:00+00:00</updated>
   <id>/work/2012/09/27/Как-добавить-ссылку-на-трекер-в-Robot-Framework</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://robotframework.googlecode.com&quot;&gt;Robot Framework&lt;/a&gt; - мощный и гибкий фреймворк для создания собственной системы тестирования. Я работаю с ним около года, и до сих пор восхищаюсь, насколько логично и удобно он устроен.&lt;/p&gt;

&lt;p&gt;Вот типичный пример: допустим, каждый тест связан с одним или несколькими тикетами в баг-трекере. Самый логичный способ установить эту связь - добавить соответствующие теги к тесту или набору тестов. Теперь в файлах отчётов будет видно, насколько успешно проходят тесты по каждому из тикетов.&lt;/p&gt;

&lt;p&gt;Сегодня я задался вопросом: а можно ли добавить в отчёты ещё и ссылку на сам тикет, чтобы можно было одним кликом перейти на соответствующую страницу в трекере?&lt;/p&gt;

&lt;p&gt;Оказалось, это делается &lt;a href=&quot;http://robotframework.googlecode.com/hg/doc/userguide/RobotFrameworkUserGuide.html#creating-links-from-tag-names&quot;&gt;совсем несложно&lt;/a&gt;. Достаточно добавить один параметр к строке запуска &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pybot&lt;/code&gt; (или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jybot&lt;/code&gt;), и ссылки на тикеты добавятся на страницу отчёта.&lt;/p&gt;

&lt;p&gt;Для наглядности я сделал небольшой пример.&lt;/p&gt;

&lt;p&gt;Создайте файл &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;taglinks.txt&lt;/code&gt; с подобным содержанием:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;*** Settings ***
Force Tags        issue-123

*** Test Cases ***
Pass
    [Tags]    issue-1234    not-an-issue
    No Operation
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;И запустите тест командой:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pybot &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--tagstatlink&lt;/span&gt; issue-&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;:http://example.com/redmine/issues/%1:Issues &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--tagstatlink&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;i&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;:http://ahitrin.github.com/:Blog &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    taglinks.txt&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Результат не только радует глаз, но и делает отчёт намного удобнее:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/robotframework-tags.png&quot; alt=&quot;pic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Во-первых, к тегам действительно добавились ссылки. Ссылка “Issues” для тега issue-123 отправит пользователя на http://example.com/redmine/issues/123, а для тега issue-1234, соответственно, на http://example.com/redmine/issues/1234.&lt;/p&gt;

&lt;p&gt;Во-вторых, видно, что можно создавать несколько ссылок на каждый тег. Главное здесь - придумать для своих тестов хороший способ именования тегов, чтобы не возникало &lt;em&gt;случайных&lt;/em&gt; коллизий (а только запланированные). Лично мне пока хватит и просто ссылок на внутренний Redmine ;)&lt;/p&gt;

&lt;p&gt;Вот и всё на сегодня. Желаю всем зелёных тестов, и побольше!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Включаем Подсветку Кода В Jekyll</title>
   <link href="/jekyll/2012/08/09/%D0%92%D0%BA%D0%BB%D1%8E%D1%87%D0%B0%D0%B5%D0%BC-%D0%BF%D0%BE%D0%B4%D1%81%D0%B2%D0%B5%D1%82%D0%BA%D1%83-%D0%BA%D0%BE%D0%B4%D0%B0-%D0%B2-Jekyll/"/>
   <updated>2012-08-09T00:00:00+00:00</updated>
   <id>/jekyll/2012/08/09/Включаем-подсветку-кода-в-Jekyll</id>
   <content type="html">&lt;p&gt;В каждой культурной среде существуют свои определённые традиции. Например, хаскеллисты традиционно пишут туториалы по использованию монад. А пользователи jekyll традиционно пишут пост “Как включить подсветку кода”. Внесу же и я свою скромную лепту в общий культурно-гумусный слой.&lt;/p&gt;

&lt;p&gt;По-умолчанию, jekyll уже поддерживает pygments, однако всё равно требуется проделать ряд телодвижений, чтобы код &lt;em&gt;действительно&lt;/em&gt; стал подсвечиваться. Вот последовательность, которая помогла мне.&lt;/p&gt;

&lt;p&gt;Сначала ставим pygments (если их ещё нет). Например, через кошерный &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;pygments&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Не забываем также обновить свои гемы - старый liquid может работать некорректно ;)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;yum update&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Проверяем, что конфиг &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt; содержит указание использовать pygments:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;pygments&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Создаём css-файл, который отвечает за стиль подсветки:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pygmentize &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; trac &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; html &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; stylesheets/pygments.css&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Мне понравился стиль trac, и вы тоже можете поиграть с расцветкой кода:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; /usr/share/pyshared/pygments/styles
__init__.py
autumn.py
borland.py
bw.py
colorful.py
default.py
emacs.py
friendly.py
fruity.py
manni.py
monokai.py
murphy.py
native.py
pastie.py
perldoc.py
tango.py
trac.py
vim.py
vs.py&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Добавляем ссылку на созданный нами файл в свой лэйаут. Я не стал мудрить, и тупо вписал одну строку в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_includes/themes/twitter/default.html&lt;/code&gt; туда же, где указаны прочие стили:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/stylesheets/pygments.css&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;На всякий случай исправляем дефолтный стиль в файле &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bootstrap.min.css&lt;/code&gt;, если у вас используется тема Twitter Bootstrap. Так рекомендуют &lt;a href=&quot;http://www.stehem.net/2012/02/14/how-to-get-pygments-to-work-with-jekyll.html&quot;&gt;здесь&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;gd&quot;&gt;-code{padding:3px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;}
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+code{padding:3px 4px;color:#d14;border:1px solid #e1e1e8;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;После этого всё должно работать.&lt;/p&gt;

&lt;h1 id=&quot;пример&quot;&gt;Пример&lt;/h1&gt;

&lt;p&gt;Для подсветки надо использовать liquid-теги. Пишем в markdown-исходник страницы что-нибудь вида:&lt;/p&gt;

&lt;p&gt;{% highlight c linenos %}&lt;br /&gt;
int main() {&lt;br /&gt;
    printf(“Hello\n”);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
{% endhighlight %}&lt;/p&gt;

&lt;p&gt;И получаем симпатичный кусок кода:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Если нам не нужна нумерация строк, то убираем слово linenos.&lt;/p&gt;

&lt;h1 id=&quot;ссылки&quot;&gt;Ссылки&lt;/h1&gt;

&lt;p&gt;Главная ссылка: &lt;a href=&quot;http://www.stehem.net/2012/02/14/how-to-get-pygments-to-work-with-jekyll.html&quot;&gt;How to get Pygments to work with Jekyll&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Слияние Репозиториев В Git</title>
   <link href="/work/2012/07/16/%D0%A1%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5-%D1%80%D0%B5%D0%BF%D0%BE%D0%B7%D0%B8%D1%82%D0%BE%D1%80%D0%B8%D0%B5%D0%B2-%D0%B2-git/"/>
   <updated>2012-07-16T00:00:00+00:00</updated>
   <id>/work/2012/07/16/Слияние-репозиториев-в-git</id>
   <content type="html">&lt;p&gt;Есть два репозитория. Задача - слить их так, чтобы файлы из одного репозитория попали в папку в другом, и при этом сохранилась вся история.&lt;/p&gt;

&lt;p&gt;Я не нашёл в сети полного описания всех шагов (может быть, плохо искал), и поэтому решил набить памятку, скомпилированную из разных источников и собственных экспериментов.&lt;/p&gt;

&lt;h2 id=&quot;суть-проблемы&quot;&gt;Суть проблемы&lt;/h2&gt;

&lt;p&gt;Допустим, у нас есть два проекта, каждый из которых живёт в отдельном репозитории.&lt;/p&gt;

&lt;p&gt;Назовём первый проект libfoo. Это архиполезная библиотека с долгой и знатной историей.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zloddey@zlosung:~/projects/libfoo$ git log --pretty=oneline
2e0560e5c43ec63096f975ea534c23d58274a341 Fix critical bug
f0d99354dd3140d826b7e77cfb4e72f251dcf81e Awesome Foo library
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;В ней много файлов, все из которых нам очень важны.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;libfoo
∟ include
  ∟ foo.h
∟ src
  ∟ foo.c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Второй проект - это mainproject, убийца Facebook, Twitter и (внезапно) Wikipedia.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zloddey@zlosung:~/projects/mainproject$ git log --pretty=oneline
88ed3223758ea3171ba4fccaf0098c85db38d348 Kill&apos;em all!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Он тоже очень большой&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mainproject
∟ src
  ∟ main.c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;И после трёх лет разработки мы поняли, что нам жизненно необходимо слить оба репозитория в один. А именно, переместить библиотеку в репозиторий проекта (в новую папку libfoo). Разумеется, при этом хочется сохранить всю историю изменений.&lt;/p&gt;

&lt;h2 id=&quot;disclaimer&quot;&gt;Disclaimer&lt;/h2&gt;

&lt;p&gt;В этой записи я провожу все изменения прямо в самих репозиториях. Но при работе с реальными репозиториями я &lt;strong&gt;крайне рекомендую&lt;/strong&gt; проводить подобные манипуляции исключительно с копиями Ваших репозиториев. Несмотря на то, что git в большинстве случаев старается сохранить пользовательские данные, некоторые из используемых нами команд способны &lt;strong&gt;бесследно&lt;/strong&gt; удалить содержимое репозитория.&lt;/p&gt;

&lt;p&gt;Поэтому проводите все эксперименты на копиях рабочих репозиториев. Тем более, что клоны в git создаются очень быстро. А когда все манипуляции будут завершены, то репозитории можно синхронизировать через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;push&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pull&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;В общем, я считаю, что предупредил Вас. Берегите свои данные!&lt;/p&gt;

&lt;h2 id=&quot;переписываем-путь-к-файлам&quot;&gt;Переписываем путь к файлам&lt;/h2&gt;

&lt;p&gt;Первый шаг на пути к слиянию репозиториев &lt;strike&gt;в сладостном экстазе&lt;/strike&gt; - это изменение пути к файлам в репозитории с библиотекой. Если сейчас файлы лежат в корне, то после наших манипуляций они должны переместиться в папку libfoo (которая будет расположена внутри репозитория libfoo).&lt;/p&gt;

&lt;p&gt;Добиться этого нам поможет термоядерная команда &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git filter-branch&lt;/code&gt;. &lt;a href=&quot;http://www.kernel.org/pub/software/scm/git/docs/git-filter-branch.html&quot;&gt;Она предназначена&lt;/a&gt; для активного редактирования истории в духе &lt;a href=&quot;http://ru.wikipedia.org/wiki/1984_(%D1%80%D0%BE%D0%BC%D0%B0%D0%BD)&quot;&gt;“1984”&lt;/a&gt;. История изменяется непосредственно через изменение (точнее, пересоздание) коммитов, поэтому все правки необратимы. Неудачное применение &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter-branch&lt;/code&gt; может отправить Ваш репозиторий на свалку.&lt;/p&gt;

&lt;p&gt;Поэтому внимательно следите за руками:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zloddey@zlosung:~/projects/libfoo$ git filter-branch --tree-filter &apos;mkdir libfoo; ls | grep -v libfoo | xargs -I{} mv {} libfoo&apos; master
Rewrite 2e0560e5c43ec63096f975ea534c23d58274a341 (2/2)
Ref &apos;refs/heads/master&apos; was rewritten
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Флаг &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--tree-filter&lt;/code&gt; командует git-у, что следующая за ним команда должна быть применена к каждому коммиту в цепочке (в нашем случае ко всей ветке master). Фильтрация пройдёт успешно только в том случае, когда команда завершается со статусом 0, и все коммиты удаётся наложить.&lt;/p&gt;

&lt;p&gt;Сама команда шелла тоже состоит из двух частей. Сначала мы создаём папку libfoo. Затем перемещаем в неё все файлы, &lt;em&gt;за исключением самой libfoo&lt;/em&gt;. Типичный пример мощи шелла: с помощью пайпа &lt;strike&gt;и чьей-то матери&lt;/strike&gt; составляем из базовых команд сложное действие, которое сделает за человека всю монотонную работу.&lt;/p&gt;

&lt;p&gt;Если мы не ошиблись в наборе команды, то git перепишет все коммиты репозитория и явит нам новую, улучшенную библиотеку libfoo:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;libfoo
∟ libfoo
  ∟ include
    ∟ foo.h
  ∟ src
    ∟ foo.c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;При этом сохранится вся история изменений (хотя, разумеется, изменятся хэши коммитов):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zloddey@zlosung:~/projects/libfoo$ git log --pretty=o
a98d0b1b6196831bf85f24517ece58a74317827b Fix critical bug
0286c0c223c611c35b8df9049a33189ab2e3d72c Awesome Foo library
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;При желании можно скастовать &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git log&lt;/code&gt; на любой конкретный файл и убедиться, что с его историей тоже всё в порядке.&lt;/p&gt;

&lt;p&gt;Теперь можно заняться и самим слиянием.&lt;/p&gt;

&lt;h2 id=&quot;объединяем-репозитории&quot;&gt;Объединяем репозитории&lt;/h2&gt;

&lt;p&gt;Осталось совсем немного. Переходим в наш уберпроект mainproject и вытягиваем все коммиты из изменённого репозитория библиотеки:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zloddey@zlosung:~/projects/mainproject$ git remote add libfoo-source ../libfoo/
zloddey@zlosung:~/projects/mainproject$ git fetch libfoo-source
warning: no common commits
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 11 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (11/11), done.
From ../libfoo
* [new branch]      master     -&amp;gt; libfoo-source/master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;git будет удивлён, что удалённый репозиторий не имеет с нашим общих коммитов, и предупредит нас об этом. Не стоит беспокоиться - всё идёт по плану.&lt;/p&gt;

&lt;p&gt;Однако сам план зависит от того, как именно Вы хотите слить две ветки. Это зависит, главным образом, от принятой в Вашей команде политики управления ветками в репозитории.&lt;/p&gt;

&lt;p&gt;Можно пойти по &lt;a href=&quot;http://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html&quot;&gt;Linus-way&lt;/a&gt; и слить две ветки через обычный merge. В таком случае создастся коммит, у которого будет два предка: один из libfoo, и один из mainproject:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zloddey@zlosung:~/projects/mainproject$ git branch
* master

zloddey@zlosung:~/projects/mainproject$ git merge libfoo-source/master
Merge made by the &apos;recursive&apos; strategy.
libfoo/include/foo.h |    1 +
1 file changed, 1 insertion(+)
create mode 100644 libfoo/include/foo.h
    create mode 100644 libfoo/src/foo.c

zloddey@zlosung:~/projects/mainproject$ git log --graph --pretty=oneline
*   6c15d2bbaab1ee5751f345ac67a9a1b2400b8476 Merge remote-tracking branch &apos;libfoo-source/master&apos;
|\
| * a98d0b1b6196831bf85f24517ece58a74317827b Fix critical bug
| * 0286c0c223c611c35b8df9049a33189ab2e3d72c Awesome Foo library
* 88ed3223758ea3171ba4fccaf0098c85db38d348 Kill&apos;em all!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Есть и другой вариант. Например, мы на своём рабочем проекте запретили коммиты с несколькими родителями (ради простой линейной истории коммитов). В таком случае надо сливать ветки через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rebase&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zloddey@zlosung:~/projects/mainproject$ git branch 
* master

zloddey@zlosung:~/projects/mainproject$ git checkout libfoo-source/master 
Note: checking out &apos;libfoo-source/master&apos;.
    (всякая болтовня git)
HEAD is now at a98d0b1... Fix critical bug

zloddey@zlosung:~/projects/mainproject$ git checkout -b new-master
Switched to a new branch &apos;new-master&apos;

zloddey@zlosung:~/projects/mainproject$ git rebase master 
First, rewinding head to replay your work on top of it...
Applying: Awesome Foo library
Applying: Fix critical bug

zloddey@zlosung:~/projects/mainproject$ git checkout master 
Switched to branch &apos;master&apos;

zloddey@zlosung:~/projects/mainproject$ git merge new-master 
Updating 88ed322..3641759
Fast-forward
libfoo/include/foo.h |    1 +
 1 file changed, 1 insertion(+)
 create mode 100644 libfoo/include/foo.h
 create mode 100644 libfoo/src/foo.c

zloddey@zlosung:~/projects/mainproject$ git branch -d new-master 
Deleted branch new-master (was 3641759).

zloddey@zlosung:~/projects/mainproject$ git log --graph --pretty=o
* 36417598b9e390c15f9671e8940fc42392b5a4fa Fix critical bug
* d0934618c8316f5190ad57b6ebc67def61a0c096 Awesome Foo library
* 88ed3223758ea3171ba4fccaf0098c85db38d348 Kill&apos;em all!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;И в любом случае не забываем подчистить удалённый репозиторий - он нам больше не нужен!&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zloddey@zlosung:~/projects/mainproject$ git remote rm libfoo-source
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;что-в-итоге&quot;&gt;Что в итоге&lt;/h2&gt;

&lt;p&gt;Мы получили, что хотели: в репозитории mainproject появилась папка libfoo со всеми файлами из проекта libfoo (надеюсь, Вы &lt;em&gt;действительно&lt;/em&gt; хотели именно этого!)&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mainproject
∟ libfoo
  ∟ include
    ∟ foo.h
  ∟ src
    ∟ foo.c
∟ src
  ∟ main.c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;При этом libfoo появился у нас не одним большим блобом, а в виде оригинальной последовательности коммитов. Так что, когда найдётся следующий баг в этой библиотеке, поиск через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git blame&lt;/code&gt; укажет на реального автора косяка, а не на человека, который сливал репозитории (впрочем, если это одно и то же лицо, я умываю руки).&lt;/p&gt;

&lt;p&gt;Мораль всей басни: переписывайте свою историю и упорядочивайте репозитории на здоровье, но только помните:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/bigbrotheriswatchingyou.jpg&quot; alt=&quot;pic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;(Картинка &lt;a href=&quot;http://ideonexus.com/2005/05/25/george-orwells-1984/&quot;&gt;отсюда&lt;/a&gt;)&lt;/small&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Микрокоммиты В Git</title>
   <link href="/work/2012/07/15/%D0%9C%D0%B8%D0%BA%D1%80%D0%BE%D0%BA%D0%BE%D0%BC%D0%BC%D0%B8%D1%82%D1%8B-%D0%B2-git/"/>
   <updated>2012-07-15T00:00:00+00:00</updated>
   <id>/work/2012/07/15/Микрокоммиты-в-git</id>
   <content type="html">&lt;p&gt;Я использую git в работе уже несколько лет, но в последние месяцы заметил, что у меня сменился способ работы с ним. Попробую изложить, как я использую git, и что с этого имею.&lt;/p&gt;

&lt;p&gt;Здесь не будет подробных примеров со скриншотами и кусками кода, объясняющими работу используемых команд git. Во-первых, мне лень, а во-вторых, с ними статья стала бы совсем гигантской. Все примеры находятся не дальше первой страницы поиска в гугле. Впрочем, если тема вдруг окажется востребованной, я вполне буду готов поработать над наглядными иллюстрациями.&lt;/p&gt;

&lt;h2 id=&quot;большие-коммиты&quot;&gt;Большие коммиты&lt;/h2&gt;

&lt;p&gt;Старый способ работы был унаследован со старых систем контроля версий. Вкратце его можно назвать “большой кусок работы”. Суть очень проста: пишем-пишем-пишем код, а когда тот готов, сохраняем его в VCS.&lt;/p&gt;

&lt;p&gt;Чем это плохо? Отсутствует сохранение промежуточного результата. Смешиваются изменения, которые приносят прогресс, и эксперименты, которые следует отбросить. Смешиваются новые фичи и рефакторинг, не изменяющий поведения. Как результат, итоговый коммит получается неряшливым и не слишком понятным.&lt;/p&gt;

&lt;p&gt;Программисты, использующие git недавно, также могут создавать коммиты в процессе работы “когда получается”. Такой вариант не сильно отличается от предыдущего. Может быть, даже хуже: в коммиты попадает промежуточный код, от которого потом отказались. На мой взгляд, это весьма вредно. Кто-то другой, копаясь в истории, может надолго смутиться, увидев неправильный код, а то даже и ошибочно принять его за образец для подражания.&lt;/p&gt;

&lt;h2 id=&quot;мелкие-коммиты&quot;&gt;Мелкие коммиты&lt;/h2&gt;

&lt;p&gt;Когда-то и я не обращал особого внимания на содержимое и порядок своих коммитов, но в последнее время, изучая git, методом тыка нащупал новый для себя подход. Оказалось, что гораздо удобнее создавать по ходу работы много-много мелких коммитов, а затем их упорядочивать и сливать друг с другом, чтобы получить финальную картину изменения. Система контроля версий тут выступает не как хранилище итогового результата, а как основной инструмент, который позволяет добиться более качественного кода.&lt;/p&gt;

&lt;p&gt;Работа с кодом у меня проходит в две фазы. Образно говоря, их можно назвать “время разбрасывать камни” и “время собирать камни”. Порой они сменяют друг друга однократно, а порой чередуются много раз подряд. Обычно это зависит от объёма изменений.&lt;/p&gt;

&lt;p&gt;Различие между фазами заключается в том, как мы обращаемся с коммитами.&lt;/p&gt;

&lt;h2 id=&quot;разбрасываем-камни&quot;&gt;Разбрасываем камни&lt;/h2&gt;

&lt;p&gt;Это простой, быстрый и весёлый этап. Мы просто создаём коммиты на каждый чих. Подчистили код? Сделали коммит. Провели рефакторинг? Сделали коммит. Добавили кусок функционала? Сделали коммит.&lt;/p&gt;

&lt;p&gt;Критически важно, чтобы в каждом коммите содержалось одно &lt;em&gt;понятное&lt;/em&gt; и &lt;em&gt;цельное&lt;/em&gt; изменение. Это даёт нам несколько ключевых преимуществ:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Так проще описать изменения. Вместо мало что говорящего сообщения “Тикет 1234” мы можем написать нечто конкретное, а-ля “Класс Fooblinator диспетчеризует клиентов по типу доставки”. Это нам потом ещё очень пригодится.&lt;/li&gt;
  &lt;li&gt;Pазные &lt;em&gt;цельные&lt;/em&gt; изменения меньше пересекаются друг с другом. Полного отсутствия пересечений, конечно, практически никогда не бывает, но к этому стоит стремиться :)&lt;/li&gt;
  &lt;li&gt;Mы можем активно экспериментировать в том же коде, просто сохраняя экспериментальные изменения в отдельные коммиты. Потом эти коммиты можно будет легко выкинуть.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Порой бывает, что даже одно цельное изменение бывает удобнее разделить на два или более. Например, мы провели какой-то рефакторинг, который затронул и старый код (существовавший до нашей фичи), и новый (созданный нами в процессе). В таком случае я отдельно сохраняю изменения в старом коде, и отдельно изменения в новом. Это упростит жизнь на второй фазе.&lt;/p&gt;

&lt;h2 id=&quot;собираем-камни&quot;&gt;Собираем камни&lt;/h2&gt;

&lt;p&gt;Если на первой фазе мы создавали много мелких коммитов, то теперь мы начинаем их объединять. Главная рабочая команда на этом этапе - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase -i&lt;/code&gt;. Также очень помогают &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git log&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git cherry&lt;/code&gt; и незаменимый &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gitk&lt;/code&gt; ;)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html&quot;&gt;Интерактивный ребэйз&lt;/a&gt; - самая приятная фича git, которую я открыл для себя в последнее время. Фактически, именно он подтолкнул меня к подобному изменению в workflow. Суть его довольно проста: после вызова команды список предназначенных для перебазирования коммитов открывается в текстовом редакторе (мой любимый vim!), где можно указать, как поступить с каждым коммитом. Можно также менять порядок коммитов, выбрасывать их (просто удалив из списка), и даже приказывать запустить между коммитами произвольные команды шелла! И после выхода из редактора git постарается выполнить все указанные действия.&lt;/p&gt;

&lt;p&gt;Так как я веду разработку в отдельной ветке, то в 95% случаев формат команды выглядит одинаково:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git rebase -i master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Чаще всего я использую следующие приёмы:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Eсли коммит содержит какой-то экспериментальный код, который не должен попасть в итоговый результат, то он просто выбрасывается.&lt;/li&gt;
  &lt;li&gt;Если коммит содержит мелкое исправление к предыдущему коммиту, то их можно слить с помощью команды &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fix&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Если коммит содержит существенное исправление к предыдущему коммиту, то их можно слить с помощью команды &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;squash&lt;/code&gt;. Это даст возможность отредактировать сообщение итогового коммита, включив в него информацию из обоих исходных коммитов. Каждый исходный коммит может содержать немного комментариев, но при слиянии мы соединяем их, и так постепенно вырастает большое и подробное описание коммита.&lt;/li&gt;
  &lt;li&gt;Чaсто коммиты требуется отсортировать, прежде чем сливать друг с другом. Для этого достаточно лишь поменять их порядок при редактировании ребэйза. Чем меньше и цельнее наши коммиты, тем легче осуществлять этот шаг.&lt;/li&gt;
  &lt;li&gt;Однако, порой становится понятно, что какой-то конкретный коммит содержит слишком много изменений. В таком случае его можно разбить на части с помощью команды &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edit&lt;/code&gt;. Ребэйз остановится на этом коммите, и мы можем удалить последний коммит и отредактировать изменения. После чего продолжить ребэйз с помощью &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase --continue&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Наличие команды &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;run&lt;/code&gt; для запуска произвольных команд шелла, безусловно, радует. Но мне пока она ни разу не требовалась.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Если наши команды слишком безумны, и коммиты не могут быть наложены в указанном порядке, то на помощь всегда готов прийти &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase --abort&lt;/code&gt;. Мой собственный опыт подобных неудач подсказывает ещё один дельный совет по применению ребэйза: не стоит увлекаться и сразу перетасовывать все коммиты с ног на голову. Если что-то пойдёт не так, то есть риск, что придётся надолго застрять в исправлении конфликтов в коде. Куда проще и быстрее выполнить ребэйз &lt;em&gt;несколько раз подряд&lt;/em&gt;, каждый раз применяя только одно изменение в структуре коммитов.&lt;/p&gt;

&lt;h2 id=&quot;профит&quot;&gt;Профит!&lt;/h2&gt;

&lt;p&gt;Подобный подход может выглядеть сложным и “избыточным”, но на практике временнЫе затраты на него совсем невелики, а выгода вполне очевидна.&lt;/p&gt;

&lt;p&gt;Никогда раньше у меня не было настолько подробных и чётких commit message’ей, которые писались бы так легко! Вся хитрость заключается в том, что я пишу их по кусочкам, а затем сливаю эти кусочки друг с другом. Каждый &lt;em&gt;небольшой&lt;/em&gt; и &lt;em&gt;цельный&lt;/em&gt; коммит описать намного проще, чем всё изменение в целом. И объединить два предложения в абзац тоже проще, чем написать их с ноля.&lt;/p&gt;

&lt;p&gt;Теперь я могу выстраивать цепочку коммитов перед изданием не в том порядке, в котором я правил код, а так, чтобы они &lt;em&gt;рассказывали историю&lt;/em&gt; читателю моего кода (сам люблю хорошие истории!). Например, расчистка кода и предварительный рефакторинг отправляются в начало истории, а сама новая фича выходит уже в конце, на подготовленную сцену. Коммит, очищенный от других изменений, проще читать и понимать, его проще перенести в другую версию продукта.&lt;/p&gt;

&lt;p&gt;Есть ли недостатки или сложности у моего подхода? Прежде всего, он требует определённой практики. Если не создавать микрокоммиты в правильный момент, то на фазе ребэйза придётся помучаться. Поэтому требуется определённое время, чтобы настроить внутреннее чутьё, которое будет подсказывать “давай, делай коммит прямо сейчас!”.&lt;/p&gt;

&lt;p&gt;Ещё бывает сложно управляться с коммитами, когда их становится слишком много. Учитывая ограниченность человеческого мозга, фаза “слишком много” может наступать уже на 15-20 мелких коммитах. О чём это обычно говорит? О том, что пришло время &lt;em&gt;собирать камни&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Интересно, есть ли аналог интерактивного ребэйза у mercurial и других соперников git? За 3-4 месяца применения этой команды я понял, что это просто мега-убойная вещь.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Лучи Ненависти Putty</title>
   <link href="/work/2012/06/28/%D0%9B%D1%83%D1%87%D0%B8-%D0%BD%D0%B5%D0%BD%D0%B0%D0%B2%D0%B8%D1%81%D1%82%D0%B8-PuTTY/"/>
   <updated>2012-06-28T00:00:00+00:00</updated>
   <id>/work/2012/06/28/Лучи-ненависти-PuTTY</id>
   <content type="html">&lt;p&gt;&lt;img src=&quot;/images/PuTTY_NoWay.png&quot; alt=&quot;pic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Вчера убил битый час на удалённую помощь коллеге, который не мог подключиться к нашему git-серверу. Бедняга работает под Windows и имел неосторожность поставить себе на машину PuTTY. Как результат, он всё никак не мог запушить к нам свою ветку.&lt;/p&gt;

&lt;p&gt;Сначала мы думали, что проблема в создаваемых PuTTY ключах. Известный факт: его формат ключей несовместим с OpenSSH. Поэтому мы рекомендуем сотрудникам создавать ключи только через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh-keygen&lt;/code&gt;. Однако здесь проблема оказалась хитрее. Не работал даже “правильный” ключ, созданный по всем правилам.&lt;/p&gt;

&lt;p&gt;Выяснилось, что PuTTY, помимо прочего, &lt;a href=&quot;http://stackoverflow.com/a/9386222/1044267&quot;&gt;подменяет переменные окружения&lt;/a&gt;, которые использует git. И делает большие удивлённые глаза, когда к нему приходит rsa-шный ключ: “я знать не знаю ваш хост, и не буду с ним соединяться”. Вот и весь сказ.&lt;/p&gt;

&lt;p&gt;В нашем случае помог простой &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unset GIT_SSH&lt;/code&gt; в гитовой консоли. Лучшим же решением проблемы будет удаление с компьютера сначала PuTTY, а затем и Windows. Эта операционка, по моему стойкому убеждению, крайне слабо подходит для разработки софта. В отличие от нашего любимого GNU/Linux.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;N.B.&lt;/strong&gt;: А ещё надо будет найти время, чтобы &lt;a href=&quot;https://github.com/w31rd0/ServerAccess/issues/6&quot;&gt;выпилить PuTTY&lt;/a&gt; из &lt;a href=&quot;https://github.com/w31rd0/ServerAccess&quot;&gt;дудки&lt;/a&gt;. Так жизнь станет ещё чуточку приятнее.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Let's Start</title>
   <link href="/test/2012/06/10/Let's-start/"/>
   <updated>2012-06-10T00:00:00+00:00</updated>
   <id>/test/2012/06/10/Let's-start</id>
   <content type="html">&lt;h1 id=&quot;статическая-генерация-сайтов&quot;&gt;Статическая генерация сайтов&lt;/h1&gt;

&lt;p&gt;По-моему, это просто прекрасно!&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Mожно создать и оттестировать запись локально, и только потом выпустить во внешний мир&lt;/li&gt;
  &lt;li&gt;Cам полностью контролируешь стиль страниц&lt;/li&gt;
  &lt;li&gt;Markdown - наше всё!&lt;/li&gt;
  &lt;li&gt;Git - наше всё в ещё большей степени&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;А вот &lt;em&gt;проблемы&lt;/em&gt; с &lt;strong&gt;буллетами&lt;/strong&gt;, после которых идёт &lt;em&gt;не-английский&lt;/em&gt; текст - это печально :(&lt;/p&gt;
</content>
 </entry>
 
 
</feed>