[{"content":"While thinking about what is happening in IT right now, one analogy suddenly came to mind. We are living through a kind of industrial revolution in programming.\nUntil recently, we programmers wrote a lot of code by hand. We spent most of our working time typing code and assembling the final system out of many small parts ourselves. Now that coding agents have appeared, writing code has become very fast and cheap. More and more work is done not through manually typing lines, but through expressing intent: what exactly you want to get in the end, how it should work, and which constraints matter. The agent then tries to turn that into an implementation.\nSomething very similar happened during the Industrial Revolution. Before factories, there was a lot of manual labor and craft-based production: things were made slowly, expensively, and depended heavily on the skill of a specific person. Then factories, mechanization, standardization, and mass production arrived. A huge layer of manual labor was automated. Craft did not disappear completely, but it became more of a niche form of work. It seems to me that the same thing is now happening to code. AI agents are automating code writing, while writing code by hand is gradually turning into a kind of craft labor.\nBut that does not mean a programmer\u0026rsquo;s work has lost its value. Its form is changing, and so are the tools. Other engineering practices have not gone anywhere. On the contrary, their value has increased dramatically. If coding used to take up 70-80% of the time in product development, now for many people it shrinks to 10%, and everything else becomes more important: product thinking, client communication, requirements analysis, architecture design, UX, communication inside the team, acceptance of the final result, QA, and product support through metrics and observing what is actually happening in the business.\nAbout ten years ago I started out as a full-stack developer. At that time the split between backend and frontend was only beginning to take shape. Why did it appear at all? Because complexity was growing. Each side developed its own difficult specifics, and keeping all of that in one person\u0026rsquo;s head became harder and harder. Over time I specialized more in backend, in the server side. Then, as products grew, I became more involved in architecture, thinking through how everything should be organized, doing team lead work, and actively participating in processes inside the team and in collaboration between teams.\nSince January of this year I have been diving much deeper into agent-based programming. And now I once again feel like a full-stack developer. My technical capabilities have grown dramatically. I am no longer afraid of building UI products in React. Before that, I was simply too lazy to dive deeply into it: my serious immersion in frontend had ended back in the jQuery era. I understand the general principles, but I did not want to spend time learning the modern tools. Now, to a large extent, that is no longer necessary. What matters most is being able to formulate tasks clearly for the agent, understand what result you want, and see potential bottlenecks and problems in advance. That is why a broad technology horizon has become much more important now.\nThe whole point can be summed up in a very simple phrase: we live in an amazing time. Our profession is changing, and we are changing with it.\n","date":"2026-04-12T08:10:14+03:00","image":"https://igorlazarev.ru/p/ai-industrial-revolution/poster_hu_76cd44aac911572d.webp","permalink":"https://igorlazarev.ru/en/p/ai-industrial-revolution/","title":"We Live in an Era of an \"Industrial Revolution\" in Programming"},{"content":"I want to give blogging another try. There are plenty of topics I would like to write about, but I no longer want to spend several hours writing and proofreading every single post.\nSo I decided to try a different format: use neural networks as a practical tool. Not to write instead of me, but to help with the draft, structure, and phrasing. At this point it is enough to dictate an idea into my phone, get a rough draft back, and start from there.\nI spent a long time thinking about what exactly I want to do myself and what I can hand over to the tools. In the end I came to a simple conclusion: I want to spend no more than half an hour on a single post. I would rather give the rest of my time to rest, family, games, experiments with vibe coding, and other things I genuinely enjoy doing. If neural networks help me fit writing into that format, then publishing at least something is better than going silent again for a long time.\nMost likely, what appears here next will not be only full-sized articles, but also concept notes: an idea, some context, a few key points. If a topic catches both my own interest and the readers\u0026rsquo;, it can always grow into a full article later.\nThere is no shortage of topics already. I want to write separately about working with files through S3, publish my presentations from the Golang course, and gradually turn each lecture into an article. And I also want to share notes about vibe coding, which I am actively learning right now.\nLet\u0026rsquo;s see whether this third attempt turns into a steady rhythm.\n","date":"2026-04-05T15:19:13+03:00","image":"https://igorlazarev.ru/p/third-attempt-to-start-blogging/poster_hu_bb519bd2d2b56e7f.webp","permalink":"https://igorlazarev.ru/en/p/third-attempt-to-start-blogging/","title":"Third Attempt to Start Blogging"},{"content":"In the previous article (Russian) I looked at the risks of nullable types. In Go such data is usually modeled with pointer fields, but there are other approaches. This post covers the Null Type pattern, which reduces the chance of nil pointer panics.\nProblem context Suppose we handle an HTTP request whose contract allows nullable types. The payload has two fields: name (null means “no name”) and count (null means “no count”). A JSON schema for this contract might look like this:\n1 2 3 4 5 6 7 8 9 10 11 type: object properties: name: type: [string, null] count: type: [object, null] properties: value: type: integer unit: type: string We can model that in Go as follows.\n1 2 3 4 5 6 7 8 9 type Request struct { Name *string `json:\u0026#34;name\u0026#34;` Count *Count `json:\u0026#34;count\u0026#34;` } type Count struct { Value int `json:\u0026#34;value\u0026#34;` Unit string `json:\u0026#34;unit\u0026#34;` } Pointer fields demand care: you must always check for nil.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var r Request // ... if r.Name != nil { *r.Name = strings.TrimSpace(*r.Name) } if r.Name == nil { fmt.Println(\u0026#34;no name\u0026#34;) } else { fmt.Println(\u0026#34;name: \u0026#34;, *r.Name) } if r.Count == nil { fmt.Println(\u0026#34;no count\u0026#34;) } else { fmt.Println(\u0026#34;count: \u0026#34;, r.Count.Value, \u0026#34; \u0026#34;, r.Count.Unit) } Comparing these fields is also non-trivial.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main() { var r1, r2 Request if EqualCount(r1.Count, r2.Count) { fmt.Println(\u0026#34;equal count\u0026#34;) } } func EqualCount(c1, c2 *Count) bool { if c1 == nil \u0026amp;\u0026amp; c2 == nil { return true } if c1 == nil || c2 == nil { return false } return *c1 == *c2 } Null Type pattern To lower nil pointer risk, you can use the Null Type pattern. Examples appear in the standard library (sql.NullString , sql.NullInt64 ) and in popular packages (e.g. google/uuid ). The usual recipe is a dedicated type with methods for encoding and decoding:\nScan(), Value() — database encoding/decoding; MarshalJSON(), UnmarshalJSON() — JSON; MarshalText(), UnmarshalText() — text; MarshalBinary(), UnmarshalBinary() — binary formats. Below is an implementation for our contract, assuming the types are used with JSON and a database.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 var jsonNull = []byte(\u0026#34;null\u0026#34;) type NullString struct { String string Valid bool } func (s *NullString) Scan(value any) error { var ns sql.NullString if err := ns.Scan(value); err != nil { return err } s.Valid = ns.Valid s.String = ns.String return nil } func (s NullString) Value() (driver.Value, error) { if !s.Valid { return nil, nil } return s.String, nil } func (s NullString) MarshalJSON() ([]byte, error) { if !s.Valid { return jsonNull, nil } return json.Marshal(s.String) } func (s *NullString) UnmarshalJSON(b []byte) error { if bytes.Equal(b, jsonNull) { s.String, s.Valid = \u0026#34;\u0026#34;, false return nil } if err := json.Unmarshal(b, \u0026amp;s.String); err != nil { return err } s.Valid = true return nil } Because Count is composite and might map to two DB columns, we skip Scan() and Value() here and only add MarshalJSON() and UnmarshalJSON().\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 type NullCount struct { Count Count Valid bool } func (c NullCount) MarshalJSON() ([]byte, error) { if !c.Valid { return jsonNull, nil } return json.Marshal(c.Count) } func (c *NullCount) UnmarshalJSON(b []byte) error { if bytes.Equal(b, jsonNull) { c.Count, c.Valid = Count{}, false return nil } if err := json.Unmarshal(b, \u0026amp;c.Count); err != nil { return err } c.Valid = true return nil } Usage example Rewriting the earlier snippet with the Null Type pattern:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var r Request r.Name.String = strings.TrimSpace(r.Name.String) if !r.Name.Valid { fmt.Println(\u0026#34;no name\u0026#34;) } else { fmt.Println(\u0026#34;name: \u0026#34;, r.Name.String) } if !r.Count.Valid { fmt.Println(\u0026#34;no count\u0026#34;) } else { fmt.Println(\u0026#34;count: \u0026#34;, r.Count.Count.Value, \u0026#34; \u0026#34;, r.Count.Count.Unit) } var r1, r2 Request if r1.Count == r2.Count { fmt.Println(\u0026#34;equal count\u0026#34;) } What changed?\nSome redundant Valid checks can go away (they used to be mandatory). Core business logic still checks Valid where it matters. Comparison becomes trivial: r1.Count == r2.Count (only if every field of the struct is comparable). Field paths get longer: r.Count.Count.Value. Initialization is a bit more verbose.\n1 2 3 4 var r Request r.Name = NullString{Valid: true, String: \u0026#34;name\u0026#34;} r.Count = NullCount{Valid: true, Count: Count{Value: 1, Unit: \u0026#34;kg\u0026#34;}} Constructors fix that.\n1 2 3 4 5 6 7 func NewNullString(s string) NullString { return NullString{Valid: true, String: s} } func NewNullCount(value int, unit string) NullCount { return NullCount{Valid: true, Count: Count{Value: value, Unit: unit}} } Then:\n1 2 3 4 var r Request r.Name = NewNullString(\u0026#34;name\u0026#34;) r.Count = NewNullCount(1, \u0026#34;kg\u0026#34;) You can also add helpers or proxy methods.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func (c NullCount) String() string { if c.Valid { return c.Count.String() } return \u0026#34;\u0026#34; } func (c NullCount) Add(next NullCount) (NullCount, error) { if c.Valid \u0026amp;\u0026amp; next.Valid { sum, err := c.Count.Add(next.Count) if err != nil { return NullCount{}, err } return NullCount{Valid: true, Count: sum}, err } if c.Valid { return NullCount{Valid: true, Count: c.Count}, nil } if next.Valid { return NullCount{Valid: true, Count: next.Count}, nil } return NullCount{}, nil } Alternative The Null Type pattern is not the only option. With many fields it can hurt readability. If a pointer is acceptable for the whole struct, nil checks inside methods may be simpler—but you must:\nuse pointer receivers on all methods; guard the receiver against nil in every method. Example for Count:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func (c *Count) String() string { if c == nil { return \u0026#34;\u0026#34; } return fmt.Sprintf(\u0026#34;%d %s\u0026#34;, c.Value, c.Unit) } func (c *Count) Add(next *Count) (*Count, error) { if c != nil \u0026amp;\u0026amp; next != nil { if c.Unit != next.Unit { return nil, fmt.Errorf(\u0026#34;unit mismatch\u0026#34;) } return \u0026amp;Count{Value: c.Value + next.Value, Unit: c.Unit}, nil } if c != nil { return c, nil } if next != nil { return next, nil } return nil, nil } Conclusion The code becomes safer and often clearer. Benefits of the Null Type pattern:\nNo nil pointer panics from these fields. Easier comparison of comparable structs (no custom Equal with nil branches). Applying it everywhere can overcomplicate the codebase. It fits primitives and small structs best; for large structs, prefer other safe-design techniques.\n","date":"2025-04-05T00:00:00Z","image":"https://igorlazarev.ru/p/go-null-type-pattern/poster_hu_96c078c34f7a2d42.jpg","permalink":"https://igorlazarev.ru/en/p/go-null-type-pattern/","title":"Reducing nil pointer panic risk with the Null Type pattern"},{"content":"I was moving sites to a new host and decided to dust off the shelves and work on my blog again. The previous version was powered by the static site generator Jekyll . That was sometimes awkward: Ruby dependencies, and occasional vulnerability alerts from GitHub’s security bot (though for local work those were never critical). Meanwhile, Hugo kept popping up in Go-related Telegram channels.\nHugo is a static site generator written in Go. According to the official site, it is “the world’s fastest framework for building websites.” Hugo ships as a normal binary (you can install it on Linux, macOS, or Windows) and includes a powerful templating system.\nTo get started you pick a theme . I ended up liking hugo-theme-stack : it is minimal without extra noise, has handy menus, categories, tags, and more—and the documentation is detailed.\nNext I adapted the repo on GitHub . I dropped the old Travis CI pipeline and replaced it with GitHub Actions. Finding actions for build and deploy took just a few minutes. I used peaceiris/actions-hugo for the build and SamKirkland/FTP-Deploy-Action for FTP deploy to hosting. The result looks like this—simple and effective!\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 jobs: deploy: runs-on: ubuntu-22.04 concurrency: group: ${{ github.workflow }}-${{ github.ref }} steps: - uses: actions/checkout@v4 with: submodules: true # Fetch Hugo themes (true OR recursive) fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod - name: Setup Hugo uses: peaceiris/actions-hugo@v3 with: hugo-version: \u0026#39;0.160.0\u0026#39; extended: true - name: Build run: hugo --minify - name: Deploy uses: SamKirkland/FTP-Deploy-Action@v4.3.5 with: local-dir: ./public/ server: ${{ secrets.server }} username: ${{ secrets.username }} password: ${{ secrets.password }} ","date":"2025-03-12T21:42:31+03:00","image":"https://igorlazarev.ru/p/new-blog-life-with-hugo/hugo-logo.svg","permalink":"https://igorlazarev.ru/en/p/new-blog-life-with-hugo/","title":"A new life for the blog on Hugo"}]