Notes

Later Ctrl + ↑

Group Work in Google Docs

For the last month I have been rewriting standard data exchange between FirstBit ERP and Bitrix for a client task. Co-workers doing the same on the Bitrix side prepared a huge mapping for this case: which field on the 1C side should be transferred to which Bitrix field (and vice versa).

They published this mapping as Google Docs tables, in the interface of which you can see users using any document at this moment — both logged in and anonymous. Anonymous ones traditionally are displaying as animals.

Colleagues generally prefer to work anonymously. As a result, I definetely used to feel like a Disney princess: you start working in the morning, and anonymous quokkas, penguins and chinchillas roll out from everywhere :-)

2022-09-18 16:25:48 work Bitrix meanwhile

Slack Advises

Slack, of course, is a thing to scold. For being slow, for having bugs, for notifications. However, I just love his stubs in case there are no new messages.

Look at this cuteness:

All done. The future is yours.

Why do we need psychotherapists at all?

You're up to date. Go forth and do great things.

Or this:

You're all read. Here is a tractor.

Geez-Louise, Slack, hold on. You're not the first who advices this, believe me.

2022-08-25 19:02:53 meanwhile

Shaken, Not Stirred

Let's speak a bit about organization of the code. If you need to describe a set of objects with common properties, think about whether this description should be divided into separate methods, each of which intended to describe one specific object?

Let's look at this example — a method that describes tabular parts of documents suitable for some task:

SupportedTypes["Document.SupplierPricesEntering"]   = "Prices";
SupportedTypes["Document.OpeningBalancesEntering"]  = "CustomerAccounts,VendorAccounts";
SupportedTypes["Document.Requisition"]              = "InventoryAndServices";

Everything seems fine, right? Descriptions are there; tabular parts are listed; splitting them by comma doesn't look expensive at all.

However, there are many documents in the method. Eventually, some colleague (or you) will need to refer to another tabular part of the document, which is already mentioned in the method. Something will distract they, they will forget to look for an existing line and something like this will turn out:

SupportedTypes["Document.SupplierPricesEntering"]   = "Prices";
SupportedTypes["Document.OpeningBalancesEntering"]  = "CustomerAccounts,VendorAccounts";
SupportedTypes["Document.Requisition"]              = "InventoryAndServices";

<...>

SupportedTypes["Document.OpeningBalancesEntering"]  = "PayrollDeductions";

As a result, a part of description will be erased, and it's good if the related functionality is covered by tests.

Conclusion? Well, you can write a helper method that will take the document type and the name of one table part as input. The helper will add items to the SupportedTypes map and ensure that the data already added is not lost.

However, if you need a better solution, then consider doing as I wrote at the beginning of this note: split the method into auxiliary methods. One method contains description for one document only (for all its tabular sections). Something like:

Procedure AddOpeningBalancesEnteringDocument(SupportedTypes)

    TabularSections = New Array;

    TabularSections.Add("CustomerAccounts");
    TabularSections.Add("VendorAccounts");
    TabularSections.Add("PayrollDeductions");

    SupportedTypes["Document.Requisition"] = StrConcat(TabularSections, ",");

EndProcedure

What do we get here? Firstly, nobody will accidentally erase the description of the document. Secondly, SonarQube will be pleased: it is highly likely to begin swearing at repeating literals with the names of tabular parts, if the helper is implemented instead of code splitting.

2022-08-07 19:04:51

Not, But!

Someone told me about the Croatian comic strips NOT/BUT at the beginning of last year, when I was seriously learning to draw. Each strip there is about some kind of traumatic or simply gloomy thought that comes into the artist’s head while working. The idea is to push a reader in the right direction and give them a more practical perspective on the situation they're in.

I quit drawing a year later, at the end of February: it became clear that I no longer had time for a hobby. But comics are completely universal; look through, even if you have no idea what a kneaded eraser is :-)

What the actual fuck?

All these endless soul-searching are well known to any professional, and the way out of them is not always obvious. Especially if you are angry, tired, and the deadline for the project expired somewhere last week.

2022-07-23 11:32:48 work meanwhile

Okay!

Oh, shoot! Just watched the video and realized that I got used to writing OK in morning statuses when asked “how do you feel today”. Like, everything is normal with me, I'm alive and doing well, nothing affects my work, etc.

Now I'm afraid to guess what colleagues thought about me. I hope they also did not know that okay in the answer to such a question does not mean okay at all :-)

2022-07-22 13:13:00 English work

Cookie Jar

The other day, I was working with cookies in one of my scripts. While searching for the optimal solution, I came across an absolutely charming (100% working, by the way) tip from StackOverflow:

You can get a CookieJar object from the session with session.cookies, and use pickle to store it to a file.

So, literally: keep your cookies in a jar, and to store them, pickle them. The jar with pickled cookies, by the way, can be put on the shelf later.

How can you not love python after that, huh?

P.S. While writing this post, I got curious – why pickle and not serialization? So, briefly: this is the way.

2022-05-28 21:34:38 Python

No More Embedded Tweets

As soon as I removed Google Fonts from my blog, I had to remove embedded tweets as well.

How did it work before? I want to link a tweet — I simply insert the link to it. The build script replaces it with an HTML block, then Twitter founds this block and replaces it with the text of the tweet (and some useful links as well). This commit shows you how it worked.

How does it work now? You're right, it does not! Twitter is blocked in Russia, so its javascript works with VPN enabled only.

Well

The solution: I had to screen all the tweets that I once referred to and add them to the notes in the form of pictures with links. Whoever needs the original will turn on VPN and go to Twitter, and the rest, at least, can read the text.

A few words about the technical side. I was too lazy to screen each tweet manually, so I was thinking about how to automate the process. At first, I came across only services that were ready to solve the problem for some pathetic ten dollars (thanks guys, maybe, one day…), but then I came across the perfect tool: a console script for Node.js.

Nothing that you don't need. Pure functionality. You give a tweet to it. It gives you a picture back:

npx snap-tweet https://twitter.com/PossumEveryHour/status/1506148678461014016

That's all. I want to donate the author, really.

I thought about attaching snap-tweet to my build script (so that it would be like before: I insert a link to a tweet, and then it generates a picture by itself and puts it where it needs to be). Decided that I'm not gonna do it. Rude violation of KISS, and indeed… There's enough entropy in the world so far. Especially now.

2022-03-21 20:25:48 blog webdev Node.js

Do Not Confuse

Code

If the type of operation is the sale of goods or real estate, then open the common form AdvancesPickFormWithVAT with the parameters defined in the PickParameters structure. The callback is EditPrepaymentOffsetEnd method, defined in the same module; pass it the AdditionalParameters structure. The form needs to be opened so that it locks the whole interface.

However, if the type of operation is a return to the supplier, then open the general form AdvancesPickFormWithVAT with the parameters defined in the PickParameters structure. The callback is the EditPrepaymentOffsetEnd method, defined in the same module; pass it the AdditionalParameters structure. The form needs to be opened so that it locks the whole interface.

I hope you won't confuse.

2022-03-16 20:09:31 code smell

No More Google Fonts

Tweet

A year ago, damn it. Okay, hello everyone! Johnny Slowpoke is here! Today we will throw out Google Fonts from my blog. I used to load the main font (PT Sans) from this service, but it makes almost no sense without cross-domain caching. Frankly speaking, the only point to do this further is if a server on which the site is running is slow, so it is faster to load fonts from Google.

I host my blog on GitHub servers and have no complaints about performance. So, I'm self-hosting PT Sans now, and you know what? The difference is dramatic. When updating a page before, there was clearly a noticeable delay between loading the page and loading the font. For now, it is barely recognizable. If you have a blog and want to try to self-host fonts — here is a cool service that solves the problem in a few clicks.

Don't forget to put a big, nice star on the repository!

2022-02-19 20:25:48 blog webdev

Totals of 2021

It is a bit late to sum up, isn't it? Well, the previous couple of months have been, um, tight, so I had no actual time to sit down and think. Now I'm on vacation for 2021 – so no time like the present. I'm writing, like, from the past.

Write out each achievement doesn't attract me at all. The year definitely turned out to be good: I did a lot of complex work on FirstBit ERP (wrote new modules, rewrote existing ones, crushed bugs, wrote functional and load tests – yes, there were countless things). This had a good effect both on the configuration itself and on the profits of my company. In addition, I passed 1C expert exam and a PostgreSQL professional exam. Hip-hip-hooray.

There were fails as well. I still feel lazy to jot them down, but the main one is obvious – I became monstrously fat and instead of losing ten kilograms (that was the original plan) – I gained five more.

Looking back, the root of most of the problems over the past year is this: I focused too much on task flow. They were all interesting in their own way, from a carefully written technical task to challenges like “find a problem in the code using your witcher's instincts”. You do one task, another, a third, a hundredth, and you do not notice that you have ignored almost everything except work. As a result, you get a lot of experience, but the world dries up to the Configurator window and there is no strength to somehow apply this experience. Unhealthy bullshit.

In short, if you imagine me as a character in some video game, it looks like this: attack and intelligence have grown noticeably over the year, but health, agility, charisma, and almost everything else in general, have dropped. Now I need to do something to deal with this mess.

You know what? I'll take ten kilometers run for a start.

Tweet

Maybe, birdie, maybe.

2022-02-08 13:25:00 work

Reuse Carefully

I'll tell you about a funny and a little embarrassing case that I took apart in January. The essence in a nutshell: a huge auto-test based on Vanessa, which is intended to check the VAT calculation, falls somewhere near the end.

I start investigating. First, I look at the screenshots in Allure: OK, the reason is obvious – in one of the documents, the conditional appearance for the field with the VAT amount doesn't work. The test expected it to be unavailable if the VAT rate is zero, but it turned out to be available somehow.

It's a mess, it needs to be fixed! I look at the condition in the code: well, it locks the field if the VAT rate is in the list of “zero” rates (list of VAT rates whose rate is equal to zero). Everything appears to be simple and logical. What the hell could possibly go wrong here?

Tweet

Well, I try to reproduce the bug manually. And there, all of a sudden, everything is nice: the conditional appearance works as it should. Floating bug, or what? I run the auto-test again, at the right moment I jump in with the debugger and find some outright garbage: in the list of “zero” rates, besides themselves, there are a bunch of empty links!

Frankly speaking, I was scratching the back of my head. The document receives this list from the common module with this code. An empty ref from here, even theoretically, cannot be obtained. Moreover, the module has the “Reuse Return Values” option ​​enabled, and the function is actually executes once somewhere at the beginning of the test, before all complex data manipulations. So, the test cannot affect it in any way, in theory.

Is it a dead end? Well, experienced colleagues have probably already guessed everything, but I had to dance around the bug and even check the standard, until got the following: return values ​​cache in 1C can be changed. I mean, not by calling RefreshReusableValues​​(), but by changing reusable values directly.

How? Well, if you get some values ​​from a cached common module, and they are not of a primitive type (string, number, etc.), you will not get the value itself, but a pointer to it somewhere in memory. You write this pointer to a variable and try to change it – you change the cache.

It's that simple, yes. This is what happened in my case: the form of another document called the method that generates the list of “zero” rates. Having received a list of values, it added an empty ref to it and used it in its logic. Thus, each time this form opens, the list cache contains more and more empty refs, which eventually broke the document at the other end of the configuration.

Tweet

In a good way, the platform should throw exceptions when developer trying to change the cache, but until this happens, you have to take care yourself. For example, when developing cached modules, return immutable data types from them (FixedStructure instead of Structure, FixedArray instead of Array, and so on). True, this is not a 100% protection: firstly, fixed types are not applicable everywhere, and secondly, even in the latest versions of the SSL, this is far from being done everywhere. Do you know many configurations not based on SSL?

Sonar also know nothing about the problem, as well as less popular software. No silver bullet, in short – check your code, look at the code of your colleagues and try not to forget about another elegant way to bang ourselves in the foot.

2022-02-08 09:00:11 work

Infostart Event 2021

Last week I visited Moscow for Infostart Event 2021:

  • Listen to numbers of interesting reports — check;
  • Meet a few cool people in real life — check;
  • Chat with friends — check!

As a result, I suddenly felt rested despite the flights, stress, and fuss. I think it's because I'm working remotely, so it was really cool to look at so many colleagues in real life (not as a stream of electrons, I mean).

In order not to get up to fly twice, I passed the first test for PostgreSQL administration in the PostgresPro office. There are three more tests ahead, and the final one does not exist at all yet — but the walking one will master the road, I guess.

2021-11-16 23:40:48 done PostgreSQL

Matryoshka

A few days ago, there was a link to 1Ci certificate in the team chat (a colleague passed their junior course). I go over and see no auth form, no errors — I just get a PDF file. Everything is OK, right? Well, then I open the file:

Error.pdf

Honestly, I am even delighted. We need to implement this UX in our products: you press, for example, the “print” button for a document, and it gives you a PDF! And inside it — a link to another PDF! And inside it — “entity is not filled”, so go fill.

You can sell it as a Russian way of dealing with errors. This is, like, for historical reasons! Blame all the damned matryoshkas, and Koschei with his needle. It is, as you remember, in the egg, and it is in the duck, which is in the rabbit, tucked into the chest, and further along the chain of nesting.

2021-10-30 17:14:56 work

Second Monitor Management via Console

I have two monitors. I use the second one during meetings in Zoom or when playing together with friends (in both cases it's convenient to bring the people's cameras there). There are more examples when it's useful, but often the device is simply idle. In order not to be distracted, I decided to turn it off.

What is the ambush here: it is inconvenient to enable or disable the second monitor via OS (a few clicks, and you need to scroll, and all the time you get confused where to go — in “Screen settings” or “Personalization”). I would like one command, and the command on a hotkey. And, ideally, from the script to rule all this.

I did not find the script, but I found a ready-made utility — MultiMonitorTool. It's free. Works under the Windows 10 without problems. The commands below turn on / off the 2nd monitor:

MultiMonitorTool.exe / disable 2
MultiMonitorTool.exe / enable 2
MultiMonitorTool.exe / switch 2

For some reason, when you turn on a monitor via enable or switch, it's positioned incorrectly sometimes (for example, before it was turned off, it was on the right, and after turning it on, it was on the left). This is fixable. First, let's save the configuration at the moment when both monitors are on:

MultiMonitorTool.exe / SaveConfig Monitors.cfg

And then, when you need to turn on the monitor, load the saved config:

MultiMonitorTool.exe / LoadConfig Monitors.cfg

The utility can do a lot more (for example, one of the commands flips application windows between monitors). Follow the link above for the description.

2021-10-16 19:55:48 workplace

Top Salaries

There is a path to a CSV file. You need to open it, read the header (first line), find the Salary column and display the top 10 salaries.

Link

Threw in my two cents just to complete the picture. If you forget about stability, readability, and performance — you can cut it in half. Here it was obvious from the very beginning that it would be shorter in bash and clearer in Python, so I just wrote it as I was used to.

What was useful: there are a bunch of examples in other languages ​​in the comments to the post. Frankly speaking, I haven’t come across some of them; it was really curious to look at the syntax and try to understand the way to solve the task.

In general, the whole story reminded Eugene Stepanishev's hobby — to write the output of the American “beer song” in all languages ​​in a row. By the way, Tonsky's issue looks as fun for me too — too trivial to seriously compare something on its basis.

What was funny: for a couple of colleagues, the 1C code caused such acute vision problems that they considered it necessary to report it :-) I partly understand the desire to assert itself on the stereotype “1C is bad, mkay?”, but it's the wrong case. The preference in syntax is nothing but a matter of taste, and besides it, the solution in 1C is no different from solutions in any other language without a built-in library for parsing CSV.

2021-10-02 16:49:35

Building The Blog on GitHub

For the last year this site has been working on a simple platform: statics, GitHub and my domain. All the pages were pre-generated and placed in a GitHub's repository with GitHub Pages enabled.

I collected statics using a script. It was working with a list of text files, pedantically arranged in folders. The script rummaged through them and generated HTML files. Then I manually pushed them to the repository.

This scheme worked well, but the number of clicks annoyed me. Here is the script pull, and there is the script pull, and then you have to tinker with the git. I wanted it to be simpler.

At some point, I figured out that not only the deployment of statics, but also the assembly can be shifted onto the shoulders of GitHub. I put together some thoughts and added two more repositories:

  1. A repository of initial data. Here I put the content of the site: the text files and a bit of metadata (page titles, dates of pages creation, tags for notes, and so on).
  2. A repository of the script for generating statics. In addition to the script itself, I put various assets here (icons, styles, manifests — in general, everything that doesn't need to be generated every time, but you have to “put” it next to the resulting HTML).

Then I wrote an action that wakes up with every push to the repository with the initial data. Shortly, its logic:

  1. Clone the repository with statics and the repository with the generating script;
  2. Update the repository with statics using the generating script;
  3. Push changes of the repository with static to the main branch;
  4. Notify the owner (me) via Telegram.

Voilà! Now, whenever the repository with the original data changes, the GitHub immediately (well, as soon as possible — within a minute) updates the repository with the site and deploys it via GitHub Pages. A bonus is a web interface to manage the site (in fact, the GitHub site). No Code. :-)

While I'm at it, I added links for editing pages directly to the site (the pencil icon in the upper-right corner). This is intended as a convenience for me, but anyone who finds a typo can send a PR. Thank you in advance!

2021-09-29 22:00:00 blog done

Diablo

The nice episode of the “We Are Doomed” IT podcast about developer burnout. No insights, but you can hear something useful for yourself. I liked the analogy with video games, somewhere close to the middle:

There was such a game — Diablo. RPG, all sorts of spells, you know. A character has mana and health, and when you have no mana to cast… Ha, I sound like a nerd! Well, nevermind. Briefly speaking, when you have no mana to cast, it's taken from your health.

— Doctor Cat

2021-09-22 21:25:48 work videogames

Hammer & Nails

I was developing payment documents in our configuration last week and came across an incredibly redundant solution to a primitive problem. Sorry, I can't keep to myself.

Just imagine: you have a document, which contains several tabular sections. Each of them has a comment field. You make a print form for this document; if at least one row of any TS contains a comment — you need to use one template 1, if there are no comments — template 2.

The task is pretty primitive, isn't it? All of us have done something like this a million times. Just read the selection, use IsBlankString() on the comment field and load the appropriate template. Coffee time!

However, instead of a short cycle, I saw this:

There is a chain of queries, in the lowest of which we scan all the TS (which, I remind you, we just raked out for printing). We are looking for comments in them, then we group the result several times and return it to the script.

Well, I'm not even talking about the load on the DBMS (I would venture to guess that this trick doesn't give a noticeable effect — after all, the selection is going to be small). It's just… Well… Checking a selection of rows is, like, five lines of code. Clear, simple, short, Sonar has no room to swear. How could you give birth to this? Because of great love for queries?

You know what? I bet that it's the correct answer. I can almost see this programmer, who has just mastered the query language more or less tolerably. He is in the absolute delight of new opportunities, so… If all you have is a hammer, everything around looks like nails.

2021-09-15 21:47:48 work code smell

No Comment

I did not notice when I gave up the habit of pedantically commenting on every method with which I have to work. This practice has some clever name for sure, but I don't care, to be honest. I mean the style when a description is added to every meaningful block of code, like this:

The meaning here is simple: when you dig in some dense legacy and run back and forth between chaotically scattered, big procedures — it is rather difficult to keep the logic of each of them in your head. The next cup of coffee will wash everything away. Therefore, such notes greatly speed up orientation on the ground, and the more time passes between approaches to the code, the more noticeable the effect.

However, at some point it became clear that this know-how is just a crutch supporting the frankly crappy code design. If you have a long procedure or function - take a breath, sit down and cut the fatty one into smaller methods. You will save both time and nerves, and you will figure out the code faster, and you will delight SonarCube.

2021-09-08 00:12:00 code smell

A Script to Sync With NAS

I posted on GitHub a Python script which I use to sync files between my PC and a NAS. I have a Synology DS220j; a lot of software comes with it, including a backup utility. However, it seems to have been made for show only: the program began to fail even at the setup stage, so I lost confidence in it pretty fast.

I suffered with the problem for some time and eventually returned to the good old rclone, which has only two problems for me. Firstly, I can't normally connect to an SMB share: yes, the login and password can be saved in Windows and rclone will use them, but OS will forget it as soon as possible. I got out of the situation by connecting my NAS as an external drive.

Secondly, I had many files and folders to sync: a directory here, a directory there, a config from there, a profile from here… In order not to produce spaghetti code, I wrote a simple script that takes sources and receivers from a config file, and then executes rclone for each pair.

2021-06-07 19:04:48 done Python workplace

Earlier Ctrl + ↓