<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Oleksandr Tokariev</title>
    <description>The latest articles on Forem by Oleksandr Tokariev (@atokarev_9).</description>
    <link>https://forem.com/atokarev_9</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3700765%2Fc7c0ecb3-4ad8-48ae-b04b-446fbca0d03c.jpg</url>
      <title>Forem: Oleksandr Tokariev</title>
      <link>https://forem.com/atokarev_9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/atokarev_9"/>
    <language>en</language>
    <item>
      <title>How to write more stable and testable code 👉 use encapsulation</title>
      <dc:creator>Oleksandr Tokariev</dc:creator>
      <pubDate>Wed, 21 Jan 2026 22:00:45 +0000</pubDate>
      <link>https://forem.com/atokarev_9/how-to-write-more-stable-and-testable-code-use-encapsulation-4553</link>
      <guid>https://forem.com/atokarev_9/how-to-write-more-stable-and-testable-code-use-encapsulation-4553</guid>
      <description>&lt;p&gt;Encapsulation is often introduced as a basic OOP principle: “hide state, expose behavior.”&lt;br&gt;&lt;br&gt;
But its real value becomes obvious when you start writing tests — especially for domain logic.&lt;/p&gt;

&lt;p&gt;Let’s look at a simple finance tracker example: a &lt;code&gt;Wallet&lt;/code&gt; that holds a balance and a list of transactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The non-encapsulated wallet
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs8304d3odz8jmf6as3p9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs8304d3odz8jmf6as3p9.png" alt=" " width="800" height="888"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21ockrfxhppvhg84dtyd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21ockrfxhppvhg84dtyd.png" alt=" " width="800" height="690"&gt;&lt;/a&gt;&lt;br&gt;
In a non-encapsulated design, the wallet is just a data holder. The business rules live elsewhere — for example, in a &lt;code&gt;WalletRepository&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Tests look reasonable at first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding &lt;code&gt;INCOME(500)&lt;/code&gt; increases the balance&lt;/li&gt;
&lt;li&gt;adding &lt;code&gt;INCOME(0)&lt;/code&gt; throws an exception&lt;/li&gt;
&lt;li&gt;adding an expense larger than the balance is rejected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tests pass, and everything seems fine.&lt;/p&gt;

&lt;p&gt;But there’s a hidden assumption: &lt;strong&gt;all callers must always go through the repository&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The moment a caller bypasses it, the model breaks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;anyone can append directly to &lt;code&gt;wallet.transactions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;anyone can set &lt;code&gt;wallet.balanceCents = -50&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The wallet can no longer defend its own invariants.&lt;br&gt;&lt;br&gt;
Tests don’t verify the domain — they verify “correct usage” by convention.&lt;/p&gt;

&lt;p&gt;This is the real cost of missing encapsulation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The encapsulated wallet
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faubh7pvrmalc7ykpvskj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faubh7pvrmalc7ykpvskj.png" alt=" " width="800" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fasasoxebk3hyp82arny4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fasasoxebk3hyp82arny4.png" alt=" " width="800" height="724"&gt;&lt;/a&gt;&lt;br&gt;
In the encapsulated version, the rules move into the &lt;code&gt;Wallet&lt;/code&gt; itself.&lt;/p&gt;

&lt;p&gt;The wallet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keeps its state private&lt;/li&gt;
&lt;li&gt;exposes read-only views (&lt;code&gt;List&amp;lt;Transaction&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;provides a single domain operation (&lt;code&gt;wallet.add(tx)&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the tests change in an important way. They no longer care &lt;em&gt;who&lt;/em&gt; calls the wallet — only &lt;em&gt;what happens&lt;/em&gt; when valid or invalid transactions are applied.&lt;/p&gt;

&lt;p&gt;Invalid inputs are rejected at the boundary.&lt;br&gt;&lt;br&gt;
Impossible states cannot be constructed.&lt;br&gt;&lt;br&gt;
Bypassing the rules is no longer an option.&lt;/p&gt;

&lt;p&gt;The key difference is not the &lt;code&gt;if&lt;/code&gt; statements — it’s &lt;strong&gt;where they live&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters
&lt;/h3&gt;

&lt;p&gt;Encapsulation turns your domain objects into the &lt;strong&gt;authority&lt;/strong&gt; over their own correctness.&lt;/p&gt;

&lt;p&gt;As a result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tests become simpler and more meaningful&lt;/li&gt;
&lt;li&gt;refactors break fewer tests&lt;/li&gt;
&lt;li&gt;invariants are enforced consistently, everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Encapsulation isn’t just about clean design.&lt;br&gt;&lt;br&gt;
It’s a way to make your tests trustworthy — and your domain harder to misuse.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>cleancode</category>
      <category>android</category>
      <category>tdd</category>
    </item>
    <item>
      <title>1 year of using AI tools in enterprise apps — here’s what I learned</title>
      <dc:creator>Oleksandr Tokariev</dc:creator>
      <pubDate>Tue, 13 Jan 2026 20:52:12 +0000</pubDate>
      <link>https://forem.com/atokarev_9/1-year-of-using-ai-tools-in-enterprise-apps-heres-what-i-learned-2dod</link>
      <guid>https://forem.com/atokarev_9/1-year-of-using-ai-tools-in-enterprise-apps-heres-what-i-learned-2dod</guid>
      <description>&lt;p&gt;I build enterprise POS Android apps — lots of architecture, lots of business rules, and plenty of &lt;em&gt;“this has to be correct.”&lt;/em&gt; 🧱&lt;br&gt;&lt;br&gt;
My AI journey started in a pretty normal place: using ChatGPT to reason through tricky architecture decisions and scaffold the boring-but-necessary code.&lt;/p&gt;

&lt;p&gt;Then it escalated fast. 🚀&lt;/p&gt;

&lt;p&gt;Once tools like Cursor made &lt;strong&gt;“chat-to-code”&lt;/strong&gt; feel native inside the editor, I noticed a shift. I stopped &lt;em&gt;creating&lt;/em&gt; classes manually and started &lt;em&gt;directing&lt;/em&gt; how they should be created. Cursor’s agent-style workflows are built to take broader tasks, plan ahead, edit across files, and even run commands — a move from &lt;strong&gt;autocomplete to execution&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I do differently now 👇
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;📚I don’t anymore write files and classes, &lt;strong&gt;I write TDD&lt;/strong&gt; for AI model.&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;I think in systems, not snippets&lt;/strong&gt;
When code generation gets cheap, architecture decisions get expensive. I spend more time on patterns, boundaries, and long-term maintainability than on low-level syntax.&lt;/li&gt;
&lt;li&gt;👀 &lt;strong&gt;I review everything&lt;/strong&gt;
AI moves fast — and it can be confidently wrong. Reading “foreign code” is now a core skill, not a nice-to-have.&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Docs are part of the toolchain&lt;/strong&gt;
Strong README files and a &lt;code&gt;/docs&lt;/code&gt; folder keep the model grounded in the application’s business logic.&lt;/li&gt;
&lt;li&gt;🧪 &lt;strong&gt;Tests protect me from becoming a human linter&lt;/strong&gt;
I lean on TDD and automated tests to shorten feedback loops and avoid spending my day QA’ing AI output.&lt;/li&gt;
&lt;li&gt;🧹 &lt;strong&gt;I treat AI output like real work in Git&lt;/strong&gt;
Frequent commits, then rebase/squash. Clean history still matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last year for me was basically this: &lt;strong&gt;try things, break things, learn the edges&lt;/strong&gt;. 🔍&lt;br&gt;&lt;br&gt;
The AI space is moving so fast that it feels like every month there’s a new tool raising the bar for what’s possible.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>android</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
