<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Darko Tasevski — Writing</title><description>Notes on building for the web, leading teams, and keeping AI close to the metal.</description><link>https://darkotasevski.dev/</link><language>en</language><item><title>A Case Against Abstraction</title><link>https://darkotasevski.dev/writing/a-case-against-abstraction/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/a-case-against-abstraction/</guid><pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I was knee-deep in a very complex project. The problem wasn&apos;t just the size of the codebase, it was the endless forest of indirection. Factory functions, Providers, Managers, Registries, Mixins; everywhere I turned, there was another layer. Following the flow of data felt less like tracing logic and more like spelunking through a cave system with no map.&lt;/p&gt;
&lt;p&gt;At one point, I leaned on an LLM to help me debug. And it failed. Not because the model was weak, but because the architecture was so fragmented and implicit that even an AI couldn&apos;t piece it together. Abstraction, the thing we&apos;ve always leaned on to tame complexity, had itself become the source of complexity.&lt;/p&gt;
&lt;p&gt;In this specific case, abstraction didn&apos;t clarify; it obscured. For both me and the AI.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Yesterday&apos;s Cure, Today&apos;s Disease&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Sufficiently advanced abstractions are indistinguishable from obfuscation.&quot; — @raganwald&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Historically, abstraction was our main defense against cognitive overload. Humans only hold so much detail in working memory, so we built neat layers that hid what we didn&apos;t need to see. Encapsulation, state management, factories, etc. they weren&apos;t just patterns, they were survival mechanisms.&lt;/p&gt;
&lt;p&gt;But in the AI era, the tradeoffs have shifted.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We hit cognitive limits quickly, while an LLM can plow through raw detail without ever getting tired.&lt;/li&gt;
&lt;li&gt;What they can&apos;t handle is &lt;em&gt;fragmentation&lt;/em&gt;. Implicit behavior scattered across dozens of files. Context broken into 15 layers of indirection.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result? Abstraction no longer reduces cognitive load. It multiplies it. It takes effort that should go into problem-solving and reroutes it into archaeology.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Case Study: When the Russian Dolls Collapse&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The project details and code samples in this case study have been obfuscated and anonymized. The patterns, complexity, and architectural issues described here are real, but the identifiers and structures are adjusted so the example remains relatable without exposing proprietary code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To see how this plays out, here&apos;s a snapshot from a real-world TypeScript/React project I worked on. It&apos;s not a hypothetical; it&apos;s a cautionary tale of what happens when abstractions pile up unchecked.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;40,000+ files in total, ~5,800 lines in core files&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;233+ files matching abstraction patterns&lt;/strong&gt; (Providers, Managers, Factories, Handlers)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;20+ Redux reducers&lt;/strong&gt; with complex interdependencies&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;15+ mixin compositions&lt;/strong&gt; in the components layer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State object with 80+ properties&lt;/strong&gt;, spanning UI, business logic, networking, and persistence&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;800+ unit test files&lt;/strong&gt; with 200-400 lines each&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;200+ E2E test files&lt;/strong&gt; with 300-500 lines each&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Abstraction Layer Explosion&lt;/h3&gt;
&lt;p&gt;A simple user action could bounce through a chain like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;User Action 
→ Component
→ Action
→ Reducer
→ Manager
→ Provider
→ Backend
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s not architecture, that&apos;s bureaucracy. Debugging usually requires going through at least 6 to 8 files just to locate the root cause.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example chain:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    Some data
  → DataManager
  → DataProvider
  → EntityManager
  → StateManager
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each layer existed to &quot;decouple&quot;, but the combined effect was Russian-doll indirection where nothing was visible without peeling back four wrappers.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Custom Frameworks on Top of Frameworks&lt;/h3&gt;
&lt;p&gt;The team even rolled its own inheritance system to work around Immutable.js limitations:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class BusinessEntity extends InheritableImmutableRecord {
  static defaultValues = {
    id: null,
    name: &apos;&apos;,
    config: Map(),
    state: Record({}),
    // ... 80+ more properties
  }
}

mergeImmutableRecordDefaults(BusinessEntity)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This meant every developer had to understand:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Immutable.js internals.&lt;/li&gt;
&lt;li&gt;The custom inheritance layer.&lt;/li&gt;
&lt;li&gt;The domain logic built on top.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The implicit behavior was so deep that neither humans nor LLMs could reason about it without constant back-and-forth exploration.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;State Bloat&lt;/h3&gt;
&lt;p&gt;The main app state was a monolith with 80+ properties across unrelated domains:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface ApplicationState {
  containers: List&amp;lt;Container&amp;gt;
  totalContainers: number
  dataItems: Map&amp;lt;ID, DataItem&amp;gt;

  // UI state
  containerRect: Rect
  scrollbarOffset: number
  isDebugModeEnabled: boolean

  // Business logic
  formFields: Map&amp;lt;string, FormField&amp;gt;
  attachments: Map&amp;lt;string, Attachment&amp;gt;
  validationErrors: Map&amp;lt;string, ValidationError&amp;gt;

  // Connection state
  connectionState: ConnectionState
  apiService: ApiService | null

  // ... 60+ more properties
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any change meant wading through 500+ lines and dozens of imports. New developers were paralyzed, and AI assistance was worse than useless, it would hallucinate or give superficial answers because it couldn&apos;t hold the whole structure in context.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Pattern Proliferation&lt;/h3&gt;
&lt;p&gt;Each entity type copied the most complex existing pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FormFieldManager + FormFieldProvider + FormFieldValueManager + FormFieldValueProvider
DataManager + DataProvider  
BookmarkManager + BookmarkProvider
CommentManager + CommentProvider
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result was not reusability but &lt;strong&gt;4x duplication&lt;/strong&gt; with inconsistent interfaces. Even if an LLM parsed one chain, knowledge didn&apos;t transfer to others.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;When LLM Models Hit the Wall&lt;/h2&gt;
&lt;p&gt;This over-abstraction wasn&apos;t just hard for me. It crippled my ability to collaborate with AI.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Context fragmentation&lt;/strong&gt;: A single feature spanned 20+ files and thousands of lines, more than even the largest context windows can practically handle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implicit flows&lt;/strong&gt;: State changes rippled through hidden chains like:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;dispatch(updateEntity(entity))
  → entityReducer updates state
  → EntityManager.validateEntity()
  → EntityProvider.syncToBackend()  
  → DataManager.handleChange()
  → StateManager.notifySubscribers()
  → Multiple UI components re-render
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No LLM could trace that end-to-end without losing coherence.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scattered logic&lt;/strong&gt;: Validation in Models, error handling in Reducers, sync logic in Providers. No single place contained the truth.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Observed impact:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bug diagnosis took &lt;strong&gt;8-10x longer&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;LLM explanations were &lt;strong&gt;70-85% less accurate&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Refactoring suggestions were blocked by tangled dependencies.&lt;/li&gt;
&lt;li&gt;Average time to onboard a new developer: 3-4 months (vs. 2-3 weeks in cleaner codebases)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The architecture didn&apos;t just slow humans, it actively blinded AI.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Test Suite Complexity and Flakiness&lt;/h2&gt;
&lt;p&gt;You might think: &quot;Well, maybe the AI could still piece things together from the test suite.&quot; Unfortunately, no. The tests were just as over-engineered as the production code and far more fragile.&lt;/p&gt;
&lt;p&gt;Thousands of tests existed, but instead of providing confidence, they became a constant source of pain.&lt;/p&gt;
&lt;h3&gt;Why the Tests Were a Crisis&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Massive mocking requirements&lt;/strong&gt;: Testing &lt;code&gt;DataManager&lt;/code&gt; meant mocking 6+ dependencies plus an entire 80-property state tree.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timing-dependent abstraction chains&lt;/strong&gt;: Async flows cascaded through Managers, Providers, Reducers, and event emitters. Slight variations caused flakiness.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implicit synchronization&lt;/strong&gt;: Tests failed unless state propagation across 5+ abstraction layers happened within arbitrary timeouts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retry culture&lt;/strong&gt;: E2E tests routinely required retries and 30-second waits, masking systemic fragility.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Adding a property to DataManager required updating:
// 1. Manager mock
// 2. Provider mock
// 3. EntityManager mock
// 4. ReducerCallbacks mock
// 5. Component test wrappers
// 6. All fixtures
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &quot;simple&quot; new feature meant updating 10-20 test files.&lt;/p&gt;
&lt;h3&gt;Quantified Pain&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o651sbt9elpizi4x048u.png&quot; alt=&quot;Image description&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;~80% of test runtime&lt;/strong&gt; was spent on setup, teardown, mocking, and retries, and not actual logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugging a single flaky test&lt;/strong&gt; often took 3+ hours.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;New developers needed 2-3 months&lt;/strong&gt; before they could write reliable tests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test maintenance overhead&lt;/strong&gt;: Nearly 50% of development time was spent debugging and keeping tests working.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;When Abstraction Actually Works&lt;/h2&gt;
&lt;p&gt;Before we dive into solutions, let&apos;s acknowledge that abstraction isn&apos;t inherently evil. There are cases where it genuinely helps:&lt;/p&gt;
&lt;h3&gt;Good Abstraction Examples&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Domain Boundaries&lt;/strong&gt;: Separating user management from payment processing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-cutting Concerns&lt;/strong&gt;: Logging, error handling, authentication&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex Algorithms&lt;/strong&gt;: When the implementation details would obscure the business logic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External APIs&lt;/strong&gt;: Wrapping third-party services with consistent interfaces&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;The Key Difference&lt;/h3&gt;
&lt;p&gt;Good abstraction &lt;strong&gt;reduces cognitive load&lt;/strong&gt; by hiding irrelevant details. Bad abstraction &lt;strong&gt;increases cognitive load&lt;/strong&gt; by hiding relevant details.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Why Humans + AI Both Need Directness&lt;/h2&gt;
&lt;p&gt;Humans think better with clarity. AI works better with explicitness. Abstraction, when it hides more than it reveals, hurts both.&lt;/p&gt;
&lt;p&gt;In the pre-AI era, abstraction bought us simplicity. In the AI era, abstraction taxes us thrice:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Humans pay in cognitive load.&lt;/li&gt;
&lt;li&gt;Machines pay in broken context.&lt;/li&gt;
&lt;li&gt;And tests pay in fragility and flakiness.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The best architecture for both is direct, explicit, domain-driven, not endlessly abstracted.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Practical Solutions&lt;/h2&gt;
&lt;h3&gt;Audit Abstractions Like Dependencies&lt;/h3&gt;
&lt;p&gt;If a layer doesn&apos;t clearly earn its keep, remove it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class DataManager {
  constructor(private provider: DataProvider) {}

  create(dataItem: DataItem) {
    return this.provider.createDataItem(dataItem);
  }
}

class DataProvider {
  createDataItem(dataItem: DataItem) {
    return backend.save(dataItem);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class DataService {
  async create(dataItem: DataItem) {
    return backend.save(dataItem);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two files collapsed into one. No loss of clarity. Massive gain in readability.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Favor Services Over Factories&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const service = ServiceFactory.get(&apos;data&apos;);
service.create(dataItem);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dataService.create(dataItem);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The factory added nothing but indirection.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Architect for AI Collaboration&lt;/h3&gt;
&lt;p&gt;Assume your pair programmer is an LLM. Flatten structures, slice state into domains, and make naming explicit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before (monolithic state):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface ApplicationState {
  containers: List&amp;lt;Container&amp;gt;;
  dataItems: Map&amp;lt;ID, DataItem&amp;gt;;
  uiState: UIState;
  connection: ConnectionState;
  // ... dozens more
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After (domain slices):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface AppState {
  page: PageState;
  data: DataState;
  ui: UIState;
  sync: SyncState;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A model can now read and explain one slice without drowning in an 80-property blob.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Stay Aware of AI&apos;s Limits&lt;/h3&gt;
&lt;p&gt;As I wrote in &lt;a href=&quot;https://dev.to/puritanic/thinking-clearly-with-llms-mental-models-and-cognitive-pitfalls-in-prompt-engineering-3dmm&quot;&gt;my post on mental models and prompt engineering&lt;/a&gt;, LLMs are pattern-matchers, not reasoners. They inherit bias, they hallucinate, and they choke on hidden indirection. Don&apos;t anthropomorphize them, and don&apos;t expect them to reconstruct intent buried under five layers of Providers and Managers.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;The 5-Minute Rule&lt;/h3&gt;
&lt;p&gt;If you can&apos;t explain what an abstraction does in 5 minutes to a new developer (or AI), it&apos;s too complex. Simplify or remove it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Abstraction Isn&apos;t Dead, But the Defaults Have Changed&lt;/h2&gt;
&lt;p&gt;Abstraction isn&apos;t going away. It was never the enemy. The problem is that too much of it, stacked without discipline, turns into an obstacle rather than a tool. What I&apos;m describing here isn&apos;t necessarily guidance only for the AI-assisted programming; it&apos;s simply a principle we should strive for regardless of whether we&apos;re coding alongside AI agents or not: &lt;strong&gt;clarity beats unnecessary indirection&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But in the AI era, the tradeoffs have become even sharper. Every abstraction has to earn its place.&lt;/p&gt;
&lt;p&gt;Does it genuinely reduce cognitive load?
Does it make the code more straightforward for humans, machines, and the test suite?
Can you explain its purpose in 5 minutes or less?&lt;/p&gt;
&lt;p&gt;If the answer is no, then the abstraction is adding weight, not lifting it.&lt;/p&gt;
&lt;p&gt;This case against abstraction isn&apos;t absolute. It&apos;s contextual. But the context has shifted. With or without AI, the best code is rarely the most abstract; it&apos;s the most &lt;em&gt;direct&lt;/em&gt;.&lt;/p&gt;
</content:encoded><category>Software Engineering</category></item><item><title>A Practical Caching Playbook</title><link>https://darkotasevski.dev/writing/practical-caching-playbook/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/practical-caching-playbook/</guid><description>A practical introduction to browser caching, how it works, when to use it, and why it matters for developers.</description><pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you&apos;ve ever pushed a change to production and your browser stubbornly kept showing you the old version, you&apos;ve experienced caching, probably not in a good way.&lt;/p&gt;
&lt;p&gt;HTTP caching is one of those topics that can seem deceptively simple (&quot;it just stores stuff locally&quot;), but the details matter. Done right, it makes sites feel lightning-fast and reduces server load. Done wrong, it causes confusing bugs and outdated resources to linger.&lt;/p&gt;
&lt;p&gt;This isn&apos;t a deep dive into every caching strategy, it&apos;s the quick, developer-focused overview I wish I had when I first started building for the web. At the end, you&apos;ll find resources for going deeper.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Why We Cache&lt;/h2&gt;
&lt;p&gt;Fetching resources over the network is slow and expensive, not just in terms of latency, but also in &lt;a href=&quot;https://web.dev/http-cache/&quot;&gt;wasted bandwidth and server strain&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Caching lets us store a copy of a resource (HTML, CSS, JS, images, fonts, etc.) so that when it&apos;s requested again, the browser can serve it directly from local storage instead of hitting the network. This reduces load times, network traffic, and the chance that a user gets a partially loaded or broken page.&lt;/p&gt;
&lt;p&gt;The process is simple in theory:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Browser requests a resource.&lt;/li&gt;
&lt;li&gt;Browser checks its cache for a valid copy.&lt;/li&gt;
&lt;li&gt;If it finds one, it serves it immediately.&lt;/li&gt;
&lt;li&gt;If not, it fetches from the server and may store a copy for next time.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&apos;s not the most flexible system, you have limited control over lifetimes, but it&apos;s built into every browser and requires minimal setup to get started.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;How Browser Caching Works&lt;/h2&gt;
&lt;p&gt;Before diving into headers and strategies, it helps to understand the mechanics of how browsers actually decide whether to serve something from cache or go back to the network.&lt;/p&gt;
&lt;h3&gt;The Cache Decision Process&lt;/h3&gt;
&lt;p&gt;When a browser requests a resource, it doesn&apos;t just blindly fetch it. Instead, it follows a decision tree that looks roughly like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Is the resource in cache?
   ├─ No -&amp;gt; Fetch from network, store in cache
   └─ Yes -&amp;gt; Is the cached copy fresh?
       ├─ Yes -&amp;gt; Serve from cache
       └─ No -&amp;gt; Send conditional request to server
           ├─ 304 Not Modified -&amp;gt; Use cached copy, update freshness
           └─ 200 OK -&amp;gt; Replace cached copy with new version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This process explains why you sometimes see &quot;304 Not Modified&quot; in DevTools: the browser already had the file, it just needed the server to confirm it was still valid.&lt;/p&gt;
&lt;h3&gt;What Defines the Cache Key&lt;/h3&gt;
&lt;p&gt;It&apos;s also worth knowing that browsers don&apos;t cache purely by URL. The cache key includes several factors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The URL itself&lt;/strong&gt; – the primary identifier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The HTTP method&lt;/strong&gt; – &lt;code&gt;GET&lt;/code&gt; requests are typically cacheable, while &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; are not.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &lt;code&gt;Vary&lt;/code&gt; header&lt;/strong&gt; – if present, it tells the cache to consider certain request headers (like &lt;code&gt;Accept-Encoding&lt;/code&gt; or &lt;code&gt;User-Agent&lt;/code&gt;) as part of the key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication state&lt;/strong&gt; – private resources may be cached separately per user to avoid cross-user leaks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Understanding this process makes the rest of the article easier to follow: headers like &lt;code&gt;Cache-Control&lt;/code&gt;, &lt;code&gt;ETag&lt;/code&gt;, and &lt;code&gt;Last-Modified&lt;/code&gt; plug directly into this decision-making loop, shaping whether the browser trusts what it already has or goes back to the server.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Key HTTP Response Headers&lt;/h2&gt;
&lt;p&gt;You&apos;ll usually configure caching at the &lt;strong&gt;response header&lt;/strong&gt; level (the browser handles request headers for you). The main players:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control&quot;&gt;&lt;code&gt;Cache-Control&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;, Defines how and for how long (&lt;em&gt;in seconds, relative to the request time&lt;/em&gt;) the browser should cache a resource (e.g., &lt;code&gt;max-age=8640000&lt;/code&gt; for 100 days).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires&quot;&gt;&lt;code&gt;Expires&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;, Sets a timestamp after which the resource is considered stale. Overridden by &lt;code&gt;Cache-Control&lt;/code&gt; if both are present.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified&quot;&gt;&lt;code&gt;Last-Modified&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;, Timestamp indicating when the resource last changed; used for conditional requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag&quot;&gt;&lt;code&gt;ETag&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;, A unique identifier (often a hash) for a resource version. Any change to the file should generate a new ETag.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Headers like &lt;code&gt;Cache-Control&lt;/code&gt; and &lt;code&gt;Expires&lt;/code&gt; rely on time-based freshness. &lt;code&gt;Last-Modified&lt;/code&gt; and &lt;code&gt;ETag&lt;/code&gt; use validation, the browser asks the server if the cached copy is still valid, and the server replies with a &lt;code&gt;304 Not Modified&lt;/code&gt; if nothing has changed.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Static assets with content hashing
Cache-Control: public, max-age=31536000, immutable

# HTML pages that reference changing assets
Cache-Control: no-cache

# API responses that are user-specific
Cache-Control: private, max-age=300

# Critical resources that must be fresh
Cache-Control: no-store

# CDN-optimized content
Cache-Control: public, max-age=3600, s-maxage=86400
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;A Closer Look at &lt;code&gt;Cache-Control&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;If there&apos;s one header that defines how caching really behaves, it&apos;s &lt;code&gt;Cache-Control&lt;/code&gt;. Think of it less as a single setting and more as a collection of knobs you can tune depending on what you&apos;re serving. The most common is &lt;code&gt;max-age&lt;/code&gt;, which tells the browser how long a file should be treated as fresh. A script with &lt;code&gt;Cache-Control: max-age=86400&lt;/code&gt; will stay valid for a full day before the browser asks the server again.&lt;/p&gt;
&lt;p&gt;Not all content should linger that long. For sensitive responses, banking APIs, for example, you&apos;d go with &lt;code&gt;no-store&lt;/code&gt;, which prevents caching altogether. &lt;code&gt;no-cache&lt;/code&gt; is more subtle: it still allows the browser to keep a copy, but forces it to check with the server before reusing it. That makes it a good fit for HTML pages that often reference new versions of CSS or JavaScript bundles.&lt;/p&gt;
&lt;p&gt;Some directives are about what happens after a file goes stale. With &lt;code&gt;must-revalidate&lt;/code&gt;, the browser has no choice but to confirm with the server before showing the file again. By contrast, &lt;code&gt;stale-while-revalidate&lt;/code&gt; gives you a performance trick: the browser can serve the old file instantly while quietly fetching the fresh one in the background. Combined with long cache times, this creates the illusion of zero-delay updates.&lt;/p&gt;
&lt;p&gt;Another important distinction is whether content is meant for just one user or many. Marking a resource as &lt;code&gt;private&lt;/code&gt; keeps it in the user&apos;s browser only, while &lt;code&gt;public&lt;/code&gt; allows intermediaries like CDNs to cache and share it across requests. This small distinction often decides whether your API response leaks into a shared cache or stays safely scoped to the user.&lt;/p&gt;
&lt;p&gt;Finally, if you&apos;re serving versioned or hashed files that will never change, you can declare them &lt;code&gt;immutable&lt;/code&gt;. This tells the browser it doesn&apos;t even need to check back with the server once a file is cached, making it perfect for assets like fonts, image sprites, or JavaScript bundles. The directive really shines when paired with modern bundlers such as webpack, Vite, or Rollup, which generate files with unique content hashes in their names (&lt;code&gt;app.3f29c1.js&lt;/code&gt;). Each new build produces new filenames, so the browser sees them as brand-new resources. The result is the best of both worlds: current files are cached aggressively and served instantly on repeat visits, while new builds automatically invalidate the old ones.&lt;/p&gt;
&lt;h3&gt;Performance Implications&lt;/h3&gt;
&lt;p&gt;Each directive affects performance differently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;immutable&lt;/code&gt;&lt;/strong&gt;: Fastest - no network requests for cached resources&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;max-age&lt;/code&gt;&lt;/strong&gt;: Fast for fresh content, validation for stale&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;no-cache&lt;/code&gt;&lt;/strong&gt;: Always requires validation request&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;no-store&lt;/code&gt;&lt;/strong&gt;: Always requires full request&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In practice, most setups end up with a mix: long-lived, immutable caching for static assets; private or no-store for anything user-specific; and short-lived or no-cache for HTML documents. The art lies in combining these directives so that your users always get a fast response without getting stuck on outdated code.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Avoiding Stale Responses&lt;/h2&gt;
&lt;p&gt;Sometimes you don&apos;t want the browser to serve a cached file, especially right after deploying a new CSS or JavaScript bundle. In development, the quick fix is to disable caching entirely in your browser&apos;s DevTools, but in production you need a more deliberate strategy.&lt;/p&gt;
&lt;p&gt;One common approach is cache busting. Instead of reusing the same filename, you append a unique query string or, more reliably, let a bundler like webpack, Vite, or Rollup generate hashed filenames automatically (&lt;code&gt;app.3f29c1.js&lt;/code&gt;). Each new build produces new filenames, so the browser treats them as brand-new resources, sidestepping the risk of serving outdated code. Another option is to adjust cache lifetimes directly: keep shorter &lt;code&gt;max-age&lt;/code&gt; values for assets that change often, while giving rarely updated resources a much longer lifetime.&lt;/p&gt;
&lt;p&gt;In my own projects, I&apos;ve leaned heavily on bundler-driven content hashing. It&apos;s simple, automatic, and works much like ETags at the HTTP level: whenever the content changes, the identifier changes with it. The result is that caches stay fresh without you having to invalidate anything manually.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Debugging and Testing Caching&lt;/h2&gt;
&lt;p&gt;One of the hardest parts of caching is that problems are often invisible, the browser quietly serves you an old file and you don&apos;t realize it until something feels &quot;off.&quot; The fastest way to confirm what&apos;s happening is to open your browser&apos;s DevTools and check the Network tab. There, the &lt;strong&gt;Size&lt;/strong&gt; column will reveal whether a file came directly from the server or was pulled from disk or memory cache. You can also toggle the &quot;Disable cache&quot; option to force every request to hit the server, which is invaluable during debugging.&lt;/p&gt;
&lt;p&gt;On the command line, tools like &lt;code&gt;curl&lt;/code&gt; or &lt;code&gt;httpie&lt;/code&gt; are equally helpful. Running &lt;code&gt;curl -I https://example.com/app.js&lt;/code&gt; shows only the response headers, letting you verify whether &lt;code&gt;Cache-Control&lt;/code&gt;, &lt;code&gt;Expires&lt;/code&gt;, or &lt;code&gt;ETag&lt;/code&gt; are set the way you expect. Adding an &lt;code&gt;If-None-Match&lt;/code&gt; or &lt;code&gt;If-Modified-Since&lt;/code&gt; header to the request simulates what the browser does during validation, and you&apos;ll know caching is working if the server replies with a simple &lt;code&gt;304 Not Modified&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For a higher-level view, performance audits such as Lighthouse or PageSpeed Insights can flag assets that aren&apos;t cached efficiently. And if you&apos;re troubleshooting right after a deployment, don&apos;t forget the basics: a normal refresh may still serve cached files, while a hard refresh forces the browser to fetch fresh copies. It&apos;s a quick sanity check that often clears up &quot;phantom bugs&quot; before you dive deeper.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugging rule of thumb:&lt;/strong&gt; when a bug seems to linger mysteriously, always suspect the cache before the code.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;How I Cache in Practice&lt;/h2&gt;
&lt;p&gt;Over time I&apos;ve settled on a few simple patterns that work well in most front-end projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For &lt;strong&gt;static assets&lt;/strong&gt; like images or fonts, I give them a long &lt;code&gt;max-age&lt;/code&gt; and serve them under hashed filenames so they can sit safely in cache for months or even years.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;CSS and JavaScript bundles&lt;/strong&gt;, I rely on hashed filenames too. That lets me use long cache durations while still getting instant invalidation whenever I deploy a new build.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;HTML&lt;/strong&gt;, I usually keep caching short, or set &lt;code&gt;no-cache&lt;/code&gt;, since it often points to fresh bundles that need to be pulled in right away.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And when something strange shows up in production? My first step is almost always to clear the cache and try again, and you&apos;d be surprised how many &quot;mystery bugs&quot; vanish as soon as stale files are out of the way.&lt;/p&gt;
&lt;p&gt;Caching is one of the easiest performance wins in web development, but it&apos;s also one of the easiest ways to create subtle bugs. The trick is to be intentional: decide what can stay cached, for how long, and how updates will reach users. Get that balance right, and caching becomes an ally rather than a source of frustration. After all, the fastest network request is the one you never have to make.&lt;/p&gt;
&lt;h2&gt;Useful Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching&quot;&gt;MDN Web Docs: HTTP caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/http-cache/&quot;&gt;Prevent unnecessary network requests with the HTTP Cache (web.dev)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ieftimov.com/post/conditional-http-get-fastest-requests-need-no-response-body/&quot;&gt;Conditional HTTP GET: The fastest requests need no response body&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webpack.js.org/guides/caching/#output-filenames&quot;&gt;Cache busting in webpack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Web dev</category></item><item><title>Thinking Clearly in Code: What Works for Me</title><link>https://darkotasevski.dev/writing/thinking-clearly-in-code/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/thinking-clearly-in-code/</guid><description>These are a few strategies I lean on to stay mentally flexible, especially when context-switching, debugging, or designing solutions gets overwhelming.</description><pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;As a developer, I&apos;ve found that staying creative isn&apos;t just for designers or product folks, it&apos;s core to solving problems, navigating ambiguity, and keeping my head clear. These four strategies help me stay mentally fresh, especially during heavy cognitive days.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Creative thinking isn&apos;t about artsy vibes; it&apos;s about problem-solving under pressure, thinking in systems, and finding new angles when the obvious path doesn&apos;t cut it. Software engineering requires this kind of creativity every day. Whether I&apos;m designing an API, debugging a race condition, or simplifying messy legacy code, the ability to think clearly and inventively is essential.&lt;/p&gt;
&lt;p&gt;Here&apos;s how I protect that part of my brain from burnout.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. I Stay (Relentlessly) Organized&lt;/h2&gt;
&lt;p&gt;When my workspace is cluttered, my brain usually is too. Starting and ending the day with a clean desk helps me feel less scattered, especially if I&apos;ve been deep in the weeds with unfamiliar code or switching contexts between projects.&lt;/p&gt;
&lt;p&gt;But it&apos;s not just about physical stuff. I usually try to plan out my week so I&apos;m not constantly surprised by deadlines. I block off time for deep work when I can. Even just writing out three priorities each morning clears space in my head for higher-order thinking. When I feel organized, I can actually &lt;em&gt;think&lt;/em&gt;, not just react.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. I Move My Body When I Feel Mentally Jammed&lt;/h2&gt;
&lt;p&gt;There&apos;s a direct link between physical movement and creative insight, especially when you&apos;re stuck. If I&apos;ve been staring at the same function for 30 minutes, it&apos;s a signal to get up. A walk, stretching, or even standing up to refill my water bottle can trigger clarity.&lt;/p&gt;
&lt;p&gt;This isn&apos;t fluff. &lt;a href=&quot;https://news.stanford.edu/stories/2014/04/walking-vs-sitting-042414&quot;&gt;Research&lt;/a&gt; shows regular movement improves both divergent thinking (multiple ideas) and convergent thinking (narrowing to one solution). Both are critical in engineering, especially in design discussions or debugging sessions.&lt;/p&gt;
&lt;p&gt;So yeah, sometimes &quot;step away from the keyboard&quot; is the best dev tip I can give myself.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. I Let Myself Experiment (Even in Code)&lt;/h2&gt;
&lt;p&gt;Not every idea has to be &quot;the one.&quot; Sometimes the path to a good solution is paved with deliberately bad ideas. I give myself space to sketch, spike, and ask dumb questions out loud, especially when exploring unfamiliar territory. Many times, I reach the answer by tinkering, stumbling into a working approach, and then tossing out the messy first draft to rebuild from scratch with a cleaner, more intentional design. Starting fresh helps me strip away accidental complexity and keep only the parts that actually move the solution forward.&lt;/p&gt;
&lt;p&gt;Collaboration helps here. Pairing with a teammate, rubber ducking, or just throwing a bad idea into the Slack void can surface better ones. If I&apos;m stuck, I often reframe the problem: &lt;em&gt;What&apos;s the simplest version? Could this be two smaller problems? What if I broke this contract entirely, and what would the consequences be?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Sometimes, getting creative is just about zooming out or flipping the problem upside-down.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. I Write Down Every Idea - Even the Weird Ones&lt;/h2&gt;
&lt;p&gt;I used to trust myself to remember clever ideas that popped into my head mid-task. I don&apos;t anymore. Now I jot down everything, in Notion, sticky notes, wherever.&lt;/p&gt;
&lt;p&gt;Half of them go nowhere, but the act of capturing them helps me stay open and curious. Sometimes two scraps connect days later into something solid. Other times, writing it down clears mental space so I can focus on what&apos;s in front of me.&lt;/p&gt;
&lt;p&gt;This habit helps me capture creative “drift” while staying grounded in what I&apos;m working on.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Reflection = Creativity Boost&lt;/h2&gt;
&lt;p&gt;Every week or so, I do a &lt;a href=&quot;https://www.todoist.com/productivity-methods/weekly-review&quot;&gt;mini-retro&lt;/a&gt;: What made me feel creative? What drained me? When did I feel most &quot;in flow&quot;? This gives me data about what conditions support my best thinking and how I can replicate them. This can feel like an extra task you&apos;d rather skip, but the long-term payoff is enormous. I’ve even set up a Notion template that automatically appears at the end of the week, making it harder to ignore and easier to build the habit.&lt;/p&gt;
&lt;p&gt;In software, creative insight doesn&apos;t always look like an eureka moment. Sometimes it&apos;s a small refactor that saves hours of debugging later. Sometimes it&apos;s knowing &lt;em&gt;when not&lt;/em&gt; to ship a feature. But either way, keeping your creative brain limber is a fundamental skill worth cultivating.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Remote work, fragmented teams, and too many Slack/Teams channels can make creative thinking feel rare. But I&apos;ve found it&apos;s something I can nurture with small, deliberate habits. Staying organized, moving around, experimenting freely, and capturing ideas as they come, that&apos;s what keeps me sharp.&lt;/p&gt;
&lt;p&gt;What do you do when your thinking starts to feel stale?&lt;/p&gt;
</content:encoded><category>Web dev</category></item><item><title>How are you writing a commit message?</title><link>https://darkotasevski.dev/writing/writing-commit-message/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/writing-commit-message/</guid><pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ll write a little bit about a topic that is not related to code, seemingly not that important, but it is quite practical in daily programming. &lt;strong&gt;How to write git commit message properly?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No way of writing is right or wrong; however, if each person in a project has its own style of a commit message, then when we look at the commit history, does it look good? Not to mention when we need to search and review commits in the commit of the previous months through commit messages without any rules.&lt;/p&gt;
&lt;p&gt;Commits that are non-consistent in a message format or don&apos;t follow any rules can be a problem because of many reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reading the commit message without knowing what the exact purpose of the commit was.&lt;/li&gt;
&lt;li&gt;When we need to summarize changes in source code after a period of development (e.g. production release)&lt;/li&gt;
&lt;li&gt;Choosing suitable new version, v1.0.0, v1.0.1, v1.1.0 or v2.0.0 etc.&lt;/li&gt;
&lt;li&gt;Trying to search through commits via regex&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://imgs.xkcd.com/comics/git_commit.png&quot; alt=&quot;obligatory xkcd&quot; /&gt;
&amp;lt;figcaption&amp;gt;Obligatory xkcd&amp;lt;/figcaption&amp;gt;&lt;/p&gt;
&lt;p&gt;Some teams decide to implement a kind of &lt;em&gt;closed&lt;/em&gt; rules to solve some of the issues caused by fragmented code commit message styles. Why closed you ask? That is because those are local to your project. For example, in my team, we had a convention to prefix commit messages and git branches with the ticket number.&lt;/p&gt;
&lt;p&gt;So are there any rules that are common to all of us and can be shared across many projects? &lt;strong&gt;Enter...&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Conventional Commits&lt;/h2&gt;
&lt;p&gt;Conventional Commits are a set of commit message writing rules that create rules that are easy to read for both machines and humans. The machines can make use of these rules, for example, in tools for automatic versioning.&lt;/p&gt;
&lt;p&gt;This set of rules corresponds to &lt;a href=&quot;https://semver.org/&quot;&gt;&lt;strong&gt;SemVer&lt;/strong&gt;&lt;/a&gt; (Semantic Version) by the way it describes features, bug fixes, code refactors, or breaking changes made in commit messages. Currently, at the time of this writing, this set of rules Conventional Commits is in version &lt;a href=&quot;https://www.conventionalcommits.org/en/v1.0.0-beta.4/&quot;&gt;1.0.0-beta.4&lt;/a&gt;, and there may be future additions. You can refer to the implementation of conventional commits in some open projects on Github, some of the bigger ones are &lt;a href=&quot;https://github.com/electron/electron&quot;&gt;Electron&lt;/a&gt;, &lt;a href=&quot;https://github.com/istanbuljs/istanbuljs&quot;&gt;IstanbulJs&lt;/a&gt;, &lt;a href=&quot;https://github.com/yargs/yargs&quot;&gt;Yargs&lt;/a&gt; and &lt;a href=&quot;https://github.com/karma-runner/karma&quot;&gt;Karma&lt;/a&gt; which, if I&apos;ve understood correctly, actually laid &lt;a href=&quot;https://karma-runner.github.io/0.10/dev/git-commit-msg.html&quot;&gt;foundations&lt;/a&gt; to semantic commits.&lt;/p&gt;
&lt;p&gt;The commit message should be structured as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;type&amp;gt;[optional scope]: &amp;lt;description&amp;gt;

[optional body]

[optional footer]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; are required by commit message, and all others are optional.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt;: keyword to classify if the commit was a feature, bugfix, refactor... Followed by the &lt;code&gt;:&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scope&lt;/code&gt;: is used to categorize commits, but answers the question: what does this commit refactor | fix? It should be enclosed in parentheses immediately after &lt;code&gt;type&lt;/code&gt;, e.g. &lt;code&gt;feat(authentication):&lt;/code&gt;, &lt;code&gt;fix(parser):&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;description&lt;/code&gt;: a short description of what was modified in the commit.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;body&lt;/code&gt;: is a longer and more detailed description, necessary when the description cannot fit one line:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;$ git commit -m &quot;feat: allow the provided config object to extend other configs

BREAKING CHANGE: `extends` key in the config file is now used for extending other config files&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;footer&lt;/code&gt;: some extra information such as the ID of the pull request, contributors, issue number(s)...&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Some examples for a short commit message as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ex1: 
git commit -m &quot;feat: implement AVOD content reels&quot;

# ex2: 
git commit -am &quot;fix: routing issue on the main page&quot;

# ex3 with scope: 
git commit -m &quot;fix(player): fix player initialization&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Semantic Versioning&lt;/h2&gt;
&lt;p&gt;Conventional Commit matches &lt;strong&gt;SemVer&lt;/strong&gt; through &lt;code&gt;type&lt;/code&gt; in the commit message. Automated versioning tooling also relies on it to decide the new version for source code. With the following convention:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fix&lt;/code&gt;: a commit of the (bug)fix type is equal to PATCH in the SemVer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;feat&lt;/code&gt;: a commit of type feature is equal to MINOR in the SemVer.&lt;/li&gt;
&lt;li&gt;Also, the keyword &lt;code&gt;BREAKING CHANGE&lt;/code&gt; in the &lt;code&gt;body&lt;/code&gt; section of the commit message will imply that this commit has a modification that makes the code no longer compatible with the previous version. Like changing the response structure of an API, the handle response part of the previous structure will of course no longer be accurate, and now we need to create an entirely new version by bumping MAJOR SemVer version.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some common &lt;code&gt;type&lt;/code&gt; uses include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;feat&lt;/code&gt;: a new feature for the user, not a new feature for a build script&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fix&lt;/code&gt;: bug fix for the user, not a fix to a build scripts&lt;/li&gt;
&lt;li&gt;&lt;code&gt;refactor&lt;/code&gt;: refactoring production code&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chore&lt;/code&gt;: updating gulp tasks etc.; no production code change&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docs&lt;/code&gt;: changes to documentation&lt;/li&gt;
&lt;li&gt;&lt;code&gt;style&lt;/code&gt;: formatting, missing semicolons, etc.; no code change&lt;/li&gt;
&lt;li&gt;&lt;code&gt;perf&lt;/code&gt;: code improved in terms of processing performance&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vendor&lt;/code&gt;: update version for dependencies, packages.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;test&lt;/code&gt;: adding missing tests, refactoring tests; no production code change&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While these are the most common types that you&apos;re going to see in the wild, nothing is stopping you from creating your own types of commits.&lt;/p&gt;
&lt;p&gt;Another bonus advantage from using semantic commits is that you can derive a sense of effort from git logs. For example, given below is a year worth of git commits in karma (master branch):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd /tmp/karma
$ git log --pretty=oneline --no-merges --since 2017/01/01 --until 2017/12/31 | cut -d &quot; &quot; -f 2 |\
cut -d &quot;(&quot; -f 1 | cut -d &quot;:&quot; -f 1 | sort -r | uniq -c | sort -nr -k1
     39 chore
     28 fix
     23 feat
     15 docs
      6 test
      2 Try
      1 refactor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the scope annotation we could further slice and dice this data into questions like which component has the most number of bug fixes?.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Commit messages must have a prefix of a type (noun form) such as feat, fix and so on, Immediately followed by scoped (if any), a colon and space.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;git commit -am &quot;test: add missing tests for promo reels&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;feat&lt;/code&gt; This type is required to use when adding a feature&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fix&lt;/code&gt; This type is required to use when fixing a bug&lt;/li&gt;
&lt;li&gt;If there is scope, the scope must be a noun that describes the area of ​​the code change and must be placed immediately after type. Eg, feat(authentication). (Examples?)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;git commit -am &quot;refactor(auth): improve refresh token logic&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The description must be a short description of the changes in the commit and must be after the type with or without a scope.&lt;/li&gt;
&lt;li&gt;A long commit can have the body right after the description, providing context for the changes. There must be a blank line between description and body.&lt;/li&gt;
&lt;li&gt;The footer can be placed immediately after the body, but there must be an empty line between the body and the footer. Footers should include extended information about commits such as related pull requests, reviewers, breaking changes. Each information on one line.&lt;/li&gt;
&lt;li&gt;Other types than &lt;code&gt;feat&lt;/code&gt; and &lt;code&gt;fix&lt;/code&gt; can be used in the commit messages.&lt;/li&gt;
&lt;li&gt;Committing breaking changes must be specified at the beginning of the body or footer with the capitalized &lt;code&gt;BREAKING CHANGE&lt;/code&gt; keyword. Followed by a colon, space, and description. For example:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;$ git commit -m &quot;feat(OAuth): add scopes for OAuth apps  

BREAKING CHANGE: environment variables now take precedence over config files.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;An exclamation mark &lt;code&gt;!&lt;/code&gt; can be added before the &lt;code&gt;type/scope&lt;/code&gt; to get attention and emphasize that the commit contains breaking change.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;So now, after the project is using Conventional Commits we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Human readable project history&lt;/li&gt;
&lt;li&gt;measuring and classification of effort&lt;/li&gt;
&lt;li&gt;way to use Semantic Release to automate versioning and automatically generate changelogs for the project with semantic-release plugins.&lt;/li&gt;
&lt;li&gt;way to use Commit Lint to lint commit messages according to Conventional Commits.&lt;/li&gt;
&lt;li&gt;and finding, filtering and analyzing the commit history is also more straightforward when you can use the regex or git tools to filter commits by &lt;code&gt;type&lt;/code&gt; or &lt;code&gt;scope&lt;/code&gt; (or both).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Some useful links&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.conventionalcommits.org/en/v1.0.0-beta.4/#specification&quot;&gt;https://www.conventionalcommits.org/en/v1.0.0-beta.4/#specification&lt;/a&gt;
&lt;a href=&quot;https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit&quot;&gt;https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit&lt;/a&gt;
&lt;a href=&quot;https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional&quot;&gt;https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional&lt;/a&gt;
&lt;a href=&quot;https://karma-runner.github.io/0.10/dev/git-commit-msg.html&quot;&gt;https://karma-runner.github.io/0.10/dev/git-commit-msg.html&lt;/a&gt;
&lt;a href=&quot;https://electronjs.org/docs/development/pull-requests#commit-message-guidelines&quot;&gt;https://electronjs.org/docs/development/pull-requests#commit-message-guidelines&lt;/a&gt;
&lt;a href=&quot;https://chris.beams.io/posts/git-commit/#seven-rules&quot;&gt;https://chris.beams.io/posts/git-commit/#seven-rules&lt;/a&gt;
&lt;a href=&quot;https://codito.in/semantic-commits-for-git&quot;&gt;https://codito.in/semantic-commits-for-git&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Really nice &lt;a href=&quot;https://unsplash.com/photos/842ofHC6MaI&quot;&gt;Cover Photo by Yancy Min&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;
</content:encoded><category>Software Engineering</category></item><item><title>Building Collaborative Interfaces: Operational Transforms vs. CRDTs</title><link>https://darkotasevski.dev/writing/building-collab-interfaces/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/building-collab-interfaces/</guid><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&quot;Can we make it work like Google Docs?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It&apos;s a request that sounds simple until you try to build it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Real-time collaboration is one of those features that feels like magic when it
works. Two people typing into the same document, edits appearing seamlessly, no
conflicts, no data loss. But under the hood, it&apos;s a carefully choreographed
system of synchronization, merging, and conflict resolution.&lt;/p&gt;
&lt;p&gt;As a senior engineer, I&apos;ve come to see that real-time editing isn&apos;t just a UI
challenge; it&apos;s an architectural one. Whether you&apos;re building a document editor,
a collaborative whiteboard, or a multiplayer code environment, you eventually
face a core decision:
&lt;strong&gt;how do you model and reconcile concurrent changes to the same data?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Two major approaches have emerged to solve this: &lt;strong&gt;Operational Transforms (&lt;a href=&quot;https://en.wikipedia.org/wiki/Operational_transformation?useskin=vector&quot;&gt;OT&lt;/a&gt;)&lt;/strong&gt; and &lt;strong&gt;Conflict-free Replicated Data Types (&lt;a href=&quot;https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type?useskin=vector&quot;&gt;CRDTs&lt;/a&gt;)&lt;/strong&gt;. Each has its philosophy, strengths, and trade-offs, and picking the right one isn&apos;t always obvious.&lt;/p&gt;
&lt;p&gt;In this post, I&apos;ll walk through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What OT and CRDTs are (in plain English)&lt;/li&gt;
&lt;li&gt;How do they solve the problem of collaboration&lt;/li&gt;
&lt;li&gt;Where each shines and where they fall short&lt;/li&gt;
&lt;li&gt;How to choose the right one for your architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whether you&apos;re building the next Notion or just curious how collaborative tech works under the hood, this post aims to give you a clear, practical foundation.&lt;/p&gt;
&lt;h2&gt;Why Collaborative Editing Is Harder Than It Looks&lt;/h2&gt;
&lt;p&gt;At a glance, real-time collaboration seems like a networking problem: sync edits between clients and you&apos;re good, right?&lt;/p&gt;
&lt;p&gt;Not quite.&lt;/p&gt;
&lt;p&gt;The real challenge lies in handling conflicting edits from multiple users, especially when those edits happen at the same time, in different places, or offline. Let&apos;s say two people insert text at the same position in a document. Whose change wins? In what order? And how do we ensure that everyone eventually sees the same result, without clobbering anyone&apos;s work?&lt;/p&gt;
&lt;p&gt;Some of the core problems collaborative systems need to solve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;: Multiple edits happening simultaneously on the same data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ordering&lt;/strong&gt;: Ensuring consistent operation ordering across clients&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conflict resolution&lt;/strong&gt;: Resolving divergent versions without manual merges&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency&lt;/strong&gt;: Keeping edits fast and responsive, even over slow or flaky connections&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Offline support&lt;/strong&gt;: Handling edits made offline and merging them correctly later&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Syncing changes efficiently across many users and devices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In traditional single-user apps, you can trust that changes happen in a known order and context. In collaborative systems, &lt;strong&gt;you lose that guarantee;&lt;/strong&gt; you now need to deal with partial views, out-of-order events, and race conditions between human intentions.&lt;/p&gt;
&lt;p&gt;This is where OT and CRDTs come in. Both are designed to &lt;strong&gt;ensure eventual consistency,&lt;/strong&gt; meaning that regardless of the edits made and their order, all users will end up with the same state. But they take radically different paths to get there.&lt;/p&gt;
&lt;p&gt;Let&apos;s explore how each works and what that means for you as an engineer.&lt;/p&gt;
&lt;h2&gt;Operational Transforms: Old School, Still Useful&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Operational Transforms (OT&lt;/strong&gt;) is one of the earliest and most battle-tested approaches to real-time collaboration. If you&apos;ve used Google Docs, you&apos;ve likely seen it in &lt;a href=&quot;https://youtu.be/yCcWpzY8dIA?feature=shared&amp;amp;t=300&quot;&gt;action&lt;/a&gt;, without realizing it.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;At its core, OT is based on this idea:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When a user performs an operation (like inserting or deleting text), that operation is sent to a central server.&lt;/li&gt;
&lt;li&gt;The server receives operations from all clients, determines their order, and transforms incoming operations to account for other concurrent operations.&lt;/li&gt;
&lt;li&gt;The transformed operations are then broadcast back to all clients, ensuring that everyone consistently applies changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Imagine two users trying to insert a character at the same position. OT resolves this by shifting one operation forward (or backward) so the result makes sense for everyone.&lt;/p&gt;
&lt;p&gt;This process of &lt;em&gt;transforming operations&lt;/em&gt; against each other is the key. It allows each client to apply changes locally and immediately, while still converging to the same final state.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Strengths&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mature and Proven&lt;/strong&gt;: OT has been used in production systems for decades.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Low-latency UX&lt;/strong&gt;: Edits appear instantly (optimistic updates), with conflicts resolved on the backend.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Efficient&lt;/strong&gt;: Operations are usually small, and the server controls ordering.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Weaknesses&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hard to Implement Correctly&lt;/strong&gt;: Writing the transformation functions (e.g., for text, trees, etc.) is non-trivial and error-prone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server-Centric&lt;/strong&gt;: OT relies on a central authority to maintain order. It&apos;s not ideal for offline-first or peer-to-peer apps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not Naturally Composable&lt;/strong&gt;: Composing OTs for rich content (like lists inside tables inside documents) becomes complex.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Real-World Usage&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Google Docs&lt;/strong&gt;: Arguably the most famous use case&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ShareDB&lt;/strong&gt;: An open-source OT-based backend for JSON/document editing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firepad&lt;/strong&gt;: Collaborative code/text editor built on Firebase and OT&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Suppose we have this initial text:&lt;/p&gt;
&lt;p&gt;&quot;Hello&quot;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User A inserts &quot;X&quot; at position 1 → insert(1, &quot;X&quot;)&lt;/li&gt;
&lt;li&gt;User B deletes character at position 2 → delete(2)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Initial:   &quot;Hello&quot;
User A:    insert(1, &quot;X&quot;)  → &quot;HXello&quot;
User B:    delete(2)       → &quot;Hllo&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without transformation, applying both ops in the wrong order could break the text.&lt;/p&gt;
&lt;p&gt;With OT, we transform each op against the other so both edits are preserved meaningfully.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hv15ufwl7m2y845ot4vm.png&quot; alt=&quot;OT example&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;CRDTs: Sync Later, Merge Automatically&lt;/h2&gt;
&lt;p&gt;While OT was designed for centralized collaboration, CRDTs (Conflict-free Replicated Data Types) were born from the needs of distributed and offline-capable systems. If OT is all about ordering and coordination, CRDTs take the opposite approach: let every device do what it wants, and merge later, automatically.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;How CRDTs Work&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;CRDTs are special data structures that are mathematically designed to be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Commutative (order doesn&apos;t matter)&lt;/li&gt;
&lt;li&gt;Associative (grouping doesn&apos;t matter)&lt;/li&gt;
&lt;li&gt;Idempotent (reapplying changes doesn&apos;t change the result)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apply changes in &lt;strong&gt;any order&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Synchronize &lt;strong&gt;without needing a central server&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Merge state from &lt;strong&gt;any number of devices;&lt;/strong&gt; even after long periods offline&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are two main types of CRDTs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Operation-based CRDTs&lt;/strong&gt;: Sync deltas (changes); smaller network load but require reliable delivery.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State-based CRDTs&lt;/strong&gt;: Sync complete state periodically; simpler merge logic but heavier payloads.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Example: If two people both insert characters at the same spot, a CRDT structure ensures both are preserved and deterministically ordered.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;&lt;strong&gt;Strengths&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Perfect for Offline-First Apps&lt;/strong&gt;: Clients can work independently and sync later.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Central Server Required&lt;/strong&gt;: Suitable for peer-to-peer and edge architectures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic Conflict Resolution&lt;/strong&gt;: You don&apos;t need to write transform functions or deal with &quot;merge hell.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Weaknesses&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Heavier Resource Usage&lt;/strong&gt;: May store tombstones, causal metadata, or complete histories.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex Data Structures&lt;/strong&gt;: Lists, nested objects, and rich text require advanced CRDT variants.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Less Mature Ecosystem&lt;/strong&gt;: Tooling is growing, but not as battle-tested as OT.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Real-World Usage&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automerge&lt;/strong&gt;: JSON-like CRDT library for JavaScript&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yjs&lt;/strong&gt;: Highly optimized CRDT framework, widely used in collaborative editors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Figma&lt;/strong&gt;: Uses a &lt;a href=&quot;https://www.figma.com/blog/how-figmas-multiplayer-technology-works/&quot;&gt;hybrid model&lt;/a&gt;, with CRDT-like properties for real-time sync&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notion&lt;/strong&gt;: Uses CRDT-based techniques for syncing changes offline&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Let&apos;s say the initial value is an empty list: &lt;code&gt;[]&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User A inserts &quot;A&quot; at position 0 (offline)&lt;/li&gt;
&lt;li&gt;User B inserts &quot;B&quot; at position 0 (offline)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Initial:   []

User A (offline):   insert(0, &quot;A&quot;)  → [&quot;A&quot;]
User B (offline):   insert(0, &quot;B&quot;)  → [&quot;B&quot;]

// When both clients sync:
CRDT merge:         → [&quot;A&quot;, &quot;B&quot;]   // or [&quot;B&quot;, &quot;A&quot;], based on internal IDs

Final (consistent): [&quot;A&quot;, &quot;B&quot;]     // same across all devices
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When both sync:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The CRDT merges these operations into a consistent order (e.g., [A, B] or [B, A]) based on causal metadata&lt;/li&gt;
&lt;li&gt;No data is lost, and both clients reach the same result: &lt;strong&gt;no manual conflict resolution required&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5b2gwdyq20p67bhpppqy.png&quot; alt=&quot;CRDT example&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Choosing the Right Model: OT or CRDT?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;There&apos;s no universal winner between Operational Transforms and CRDTs, it all comes down to your product&apos;s &lt;strong&gt;needs, constraints, and future roadmap&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Below is a practical guide to help you choose the right approach.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Decision Criteria&lt;/strong&gt;&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;1. Do users need to collaborate offline?&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Yes&lt;/strong&gt; → &lt;strong&gt;CRDTs&lt;/strong&gt; are the better fit. They allow users to work offline and merge changes later without conflicts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No&lt;/strong&gt; (always-online app) → &lt;strong&gt;OT&lt;/strong&gt; may be simpler to implement and more efficient.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;2. Is your system centralized or peer-to-peer?&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Centralized: &lt;strong&gt;OT&lt;/strong&gt; is optimized for client-server topologies.&lt;/li&gt;
&lt;li&gt;Decentralized or P2P: &lt;strong&gt;CRDTs&lt;/strong&gt; are built for this, with no need for a central authority.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;3. Is document fidelity critical (e.g., rich text, layout precision)?&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OT&lt;/strong&gt; works well for linear content (like text), but complex structures require a lot of custom work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CRDTs&lt;/strong&gt; support nested and structured data (JSON, trees), but often need more memory and tuning.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;4. How much control do you have over the client environment?&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Full control (e.g., internal tool or company-wide app): &lt;strong&gt;OT&lt;/strong&gt; might be fine, since you can enforce coordination rules.&lt;/li&gt;
&lt;li&gt;Public, multi-device, or mobile-heavy apps: &lt;strong&gt;CRDTs&lt;/strong&gt; handle edge cases like clock skew, unreliable networks, and user churn more gracefully.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;5. Is data merge correctness more important than sync speed?&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;For instant collaboration with low conflict rates: &lt;strong&gt;OT&lt;/strong&gt; can be faster.&lt;/li&gt;
&lt;li&gt;For resilience and correctness even under messy conditions: &lt;strong&gt;CRDTs&lt;/strong&gt; shine.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ Important: these models aren&apos;t mutually exclusive. Some modern tools (like Notion or Figma) use &lt;strong&gt;hybrid approaches&lt;/strong&gt;: OT for performance-critical paths, CRDTs for background sync and recovery.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;strong&gt;Closing Thoughts: Choose the Right Tool for the Right Collaboration&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Real-time collaboration is no longer a niche feature, it&apos;s a baseline expectation in modern productivity tools. But building it is anything but trivial.&lt;/p&gt;
&lt;p&gt;Both Operational Transforms and Conflict-free Replicated Data Types offer robust solutions to the problem of concurrent edits, but they approach it from fundamentally different angles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OT&lt;/strong&gt; shines in environments where coordination is possible and performance is critical.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CRDTs&lt;/strong&gt; excel when flexibility, offline support, and distributed trust are essential.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As engineers, our job isn&apos;t to chase elegance or novelty, it&apos;s to ship systems that work for real people in the real world. Understanding the trade-offs between OT and CRDTs can help you build collaborative experiences that don&apos;t just sync; they scale, they recover, and they &lt;em&gt;feel&lt;/em&gt; right.&lt;/p&gt;
&lt;p&gt;Bottom line is: You don&apos;t just choose a sync algorithm; you choose a &lt;strong&gt;collaboration philosophy&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OT&lt;/strong&gt; is about control and coordination.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CRDTs&lt;/strong&gt; are about trust and eventual harmony.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Either way, it&apos;s not just about the data structure; it&apos;s about how your users interact, when they connect, and what they expect.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Further Reading &amp;amp; Resources&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CRDTs&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fission.codes/blog/crdts-for-mortals/&quot;&gt;CRDTs for Mortals - Fission&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/automerge/automerge&quot;&gt;Automerge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yjs/yjs&quot;&gt;Yjs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://martin.kleppmann.com/papers/ot.pdf&quot;&gt;Martin Kleppmann - &quot;A Critique of the Operational Transformation Approach&quot;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OT&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/share/sharedb&quot;&gt;ShareDB (open-source OT backend)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://neil.fraser.name/writing/ot/&quot;&gt;Operational Transformation Explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://patents.google.com/patent/US6289433B1/en&quot;&gt;Google&apos;s OT patent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;General&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nutrient.io/guides/web/annotations/synchronization/&quot;&gt;Synchronizing annotations across users, devices, and sessions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nutrient.io/demo/instant-collaboration?i=persistent_UCtb7lbOA2MeAFiJ5AYjF8E3xRHB0wob.vF1P-9e_Dp67lL_0UJpykw.5J5K&quot;&gt;Instant Collaboration Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.inkandswitch.com/local-first/&quot;&gt;Ink &amp;amp; Switch - Local-first Software&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dataintensive.net/&quot;&gt;Designing Data-Intensive Applications by Martin Kleppmann&lt;/a&gt; &lt;em&gt;(Ch. 5-9 dive into consistency models and CRDTs)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Software Engineering</category></item><item><title>Coding in the Age of AI</title><link>https://darkotasevski.dev/writing/coding-in-the-age-of-ai/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/coding-in-the-age-of-ai/</guid><description>AI isn&apos;t just hype, it&apos;s changing how I code, debug, and ship software every day.</description><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When ChatGPT first started making waves, I was skeptical. I figured it was
another shiny tech fad that might be fun to play with, but wouldn&apos;t really stick
in my day-to-day work. Fast forward, and now I can&apos;t imagine programming without
some form of AI in my toolkit.&lt;/p&gt;
&lt;p&gt;I&apos;m not saying AI has replaced my skills, far from it. But it &lt;em&gt;has&lt;/em&gt; reshaped how
I approach problems, especially the boring, repetitive, or purely mechanical
parts of development. Here&apos;s what that looks like from my side of the keyboard.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Before AI&lt;/h2&gt;
&lt;p&gt;If you&apos;ve been coding long enough, you know the &quot;classic&quot; dev cycle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Plan&lt;/strong&gt;: Define the problem, sketch out solutions, argue with teammates on naming conventions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code&lt;/strong&gt;: Write everything from scratch, guided by docs and hard-earned experience.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debug&lt;/strong&gt;: Stare at logs, sprinkle &lt;code&gt;console.log&lt;/code&gt; like holy water, and pray.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test&lt;/strong&gt;: Scaffold unit tests by hand, often repeating the same setup boilerplate for every new module.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimize&lt;/strong&gt;: Refactor with a mix of pride and dread (dread optional - unless you don&apos;t have tests).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document&lt;/strong&gt;: Write docs, knowing half the team won&apos;t read them until they&apos;re in trouble.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This worked, but it was slow. The bottlenecks were everywhere: manual lookups,
repetitive boilerplate, and long debugging cycles.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;After AI&lt;/h2&gt;
&lt;p&gt;Now, the flow feels different.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Coding feels lighter&lt;/strong&gt; because AI can autocomplete, scaffold, or suggest idiomatic patterns on the fly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugging is faster&lt;/strong&gt; because I can paste an error trace and get a plausible diagnosis without trawling Stack Overflow for an hour.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing is quicker&lt;/strong&gt;. Last week, Cursor wrote around 90% of my test stubs in seconds. I still shaped the edge cases and assertions, but the grunt work was already done.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation isn&apos;t a chore&lt;/strong&gt;, because tools can generate a first draft as I go, which I then tweak.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learning curves are shorter&lt;/strong&gt; when I can have an AI &lt;a href=&quot;https://dev.to/puritanic/making-new-languages-click-with-llms-20e5&quot;&gt;walk me through a new framework&lt;/a&gt; like a patient senior dev.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I mostly use ChatGPT, Cursor, and occasionally Claude Code. The tool matters less than the workflow you wrap around it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;How I Actually Use AI in My Work&lt;/h2&gt;
&lt;h3&gt;Coding&lt;/h3&gt;
&lt;p&gt;I treat AI like a pair programmer who&apos;s great at boilerplate but still needs oversight. It&apos;s amazing for repetitive patterns such as CRUD endpoints, config files, and unit test scaffolds. I keep the final say, but letting AI start the draft saves me mental energy for the tricky parts.&lt;/p&gt;
&lt;h3&gt;Debugging&lt;/h3&gt;
&lt;p&gt;Instead of reading a wall of stack trace alone, I throw it into AI and say, &quot;Explain what&apos;s happening and why this might be failing.&quot; It&apos;s not always perfect (especially for legacy projects with a lot of spaghetti code), but it often points me toward the right layer of the problem faster than blind searching.&lt;/p&gt;
&lt;h3&gt;Documentation&lt;/h3&gt;
&lt;p&gt;I&apos;ve stopped writing all the docs from scratch. I&apos;ll have AI generate inline comments, API usage examples, or even a draft README from the code itself. Then I refine. The end result is better because I&apos;m starting from something structured instead of a blank page.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Security is Still on Me&lt;/h2&gt;
&lt;p&gt;If there&apos;s one big &quot;don&apos;t,&quot; it&apos;s handing over sensitive code or credentials. I keep anything proprietary out of prompts unless I&apos;m working with a trusted, privacy-conscious tool. When I &lt;em&gt;do&lt;/em&gt; need to share something delicate, I anonymize it or reduce it to the smallest reproducible snippet.&lt;/p&gt;
&lt;p&gt;Some rules I follow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stick to reputable AI providers and disable code sharing for proprietary projects.&lt;/li&gt;
&lt;li&gt;Strip or obfuscate sensitive data before pasting code in GPT.&lt;/li&gt;
&lt;li&gt;Keep the AI software updated to avoid vulnerabilities&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Advice If You&apos;re Just Starting&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start small&lt;/strong&gt;: use AI for a specific part of your workflow, not everything at once.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stay in the loop&lt;/strong&gt;: always read and understand generated code before shipping. If something seems off, then make sure you know what and why LLM is suggesting to do something.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ask, don&apos;t just paste&lt;/strong&gt;: the more context you give AI, the better it performs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep learning&lt;/strong&gt;: use AI to &lt;em&gt;understand&lt;/em&gt; solutions, not just implement them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treat it like a teammate&lt;/strong&gt;: collaborate, don&apos;t outsource thinking.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Watch out for prompt fixation&lt;/strong&gt;. LLMs are great, &lt;a href=&quot;https://dev.to/puritanic/thinking-clearly-with-llms-mental-models-and-cognitive-pitfalls-in-prompt-engineering-3dmm&quot;&gt;but they’re not infallible&lt;/a&gt;. Don’t get stuck repeating prompts that used to work. Mix up your phrasing, try new structures, and stay reflective instead of falling into autopilot&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;The Bottom Line&lt;/h2&gt;
&lt;p&gt;AI hasn&apos;t made me obsolete. It&apos;s made me faster, more adaptable, and frankly, more willing to experiment. I still need to understand the problems I&apos;m solving, but I can spend more of my time on architecture, design, and creative solutions instead of boilerplate and tedious debugging.&lt;/p&gt;
&lt;p&gt;In a way, AI hasn&apos;t changed &lt;em&gt;what&lt;/em&gt; programming is for me. It&apos;s still about problem-solving. It&apos;s just changed the ratio of my time between &quot;figuring stuff out&quot; and &quot;getting stuff done.&quot;&lt;/p&gt;
</content:encoded><category>Software Engineering</category></item><item><title>Improving your (Web) Dev Foo</title><link>https://darkotasevski.dev/writing/improving-web-foo/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/improving-web-foo/</guid><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been writing this since the last year, and in the end, I wasn&apos;t sure if I should publish it as this is mostly just a rant. Hopefully, some of you might find something interesting here as I wrote up some of the stuff I&apos;ve learned and I&apos;m doing in practice to keep writing effective and clean code.&lt;/p&gt;
&lt;h2&gt;Editor/IDE&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;If you are starting learning about web development, you can&apos;t go wrong, pick any code editor and code on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Currently, for web development, there are many choices when you come to choosing the editor for your work. I use Webstorm/Xcode combo for work and VS Code/Vim for my stuff. From my experience, my suggestion would be that beginner devs start learning with a simple editor without many plugins, such as VS Code, Notepad ++ or Sublime Text, typing out all those keywords and language methods by hand helps a lot with memorizing/learning all that stuff. Once you feel comfortable with a language you&apos;re writing your code with, you can switch to full-blown IDE like Webstorm or plugins enhanced VS Code.&lt;/p&gt;
&lt;h2&gt;Linters &amp;amp; Formatters&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;TLDR: Use Eslint and Prettier&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When your code base gets bigger, it&apos;s also more challenging to keep an eye on all those lines, and syntax errors problems creep in. By highlighting problematic code, undeclared variables, or missing imports, your productivity can be increased a lot and is going to save a lot of time and many nerves as well.&lt;/p&gt;
&lt;p&gt;Using &lt;a href=&quot;https://eslint.org/&quot;&gt;Eslint&lt;/a&gt; from a very start would also help a lot with learning Js, as it will force you to build healthy habits and write clean code. Over the years, I&apos;ve tailored my version of the eslint rules based on &lt;a href=&quot;https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb&quot;&gt;eslint-config-airbnb&lt;/a&gt;, but lately, I&apos;ve been looking into &lt;a href=&quot;https://github.com/wesbos/eslint-config-wesbos&quot;&gt;Wes Bos&apos;s eslint config&lt;/a&gt; and would probably give it a go in a some of my future projects.&lt;/p&gt;
&lt;p&gt;Beside Eslint I&apos;m using &lt;a href=&quot;https://prettier.io/&quot;&gt;Prettier&lt;/a&gt; for code formatting, combined with &lt;a href=&quot;https://github.com/typicode/husky&quot;&gt;husky&lt;/a&gt; and &lt;a href=&quot;https://github.com/okonet/lint-staged&quot;&gt;lint-staged&lt;/a&gt; for automating linting/formatting as precommit hooks.&lt;/p&gt;
&lt;h2&gt;The optimal directory structure&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Dan Abramov&apos;s &lt;a href=&quot;http://react-file-structure.surge.sh/&quot;&gt;solution&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have mixed feelings about this topic. The only thing that I&apos;m sure of is that there is no right solution.
Every application is different in some way or another, and each project has its own distinct needs. How we structure our applications should change based on the needs of the project, just like the technologies we choose.&lt;/p&gt;
&lt;p&gt;Do not try to optimize the project structure from the beginning of the project, but keep in mind that changing the project structure later in the project can be a problem in VCS because of history rewriting.&lt;/p&gt;
&lt;p&gt;That being said, &lt;em&gt;don&apos;t overthink it&lt;/em&gt;. Pick a folder structure that works for your application. If you’re spending a massive amount of time organizing and reorganizing components, containers, styles, reducers, sagas, you’re doing it wrong.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;File naming&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Rant incoming&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Regarding file naming, one rule that I find immensely useful is this: &lt;em&gt;name your file the same as the thing you’re exporting from that file&lt;/em&gt;. I can&apos;t stress enough how angry I feel when I have hundreds on index.js files in a poorly structured project, and finding some chunk of logic takes so much time, which feels wasted...&lt;/p&gt;
&lt;p&gt;It staggers me that some people are happy to work like this.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/2pvo6f8l3liey1suhe9s.png&quot; alt=&quot;indexes everywhere&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Even if your IDE is clever and puts the directory in the tab name for non-unique filenames, you still have a bunch of redundancy there, and will run out of tab room sooner, and you still can’t type the filename to open it. Having said that, I understand that there is the reasoning behind this — It’s a definite trade-off. Shorter import statements vs. file names that match exports.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Not a worthy trade-off, in my opinion.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In my case, for the last two or three years, I&apos;m mostly using CRA&apos;s project structure, with a few modifications, like adding a &lt;code&gt;redux/&lt;/code&gt; or &lt;code&gt;sagas/&lt;/code&gt; dir for state management logic and moving all &lt;code&gt;jsx/tsx&lt;/code&gt; files to &lt;code&gt;components/&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Debugging&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Just &lt;code&gt;console.log&lt;/code&gt; anywhere where you think there is a code &quot;smell.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Writing about debugging can be a post on its own, yet there is a lot of already excellent &lt;a href=&quot;https://developers.google.com/web/tools/chrome-devtools/javascript/&quot;&gt;posts&lt;/a&gt; and &lt;a href=&quot;https://frontendmasters.com/courses/debugging-javascript/&quot;&gt;courses&lt;/a&gt; on Js debugging so I&apos;ll keep it short.&lt;/p&gt;
&lt;p&gt;Many devs would say that using debugger looks more professional, but the &lt;code&gt;console.log&lt;/code&gt; is something I&apos;m using the most for a quick debugging. I&apos;m lately working on the apps for Smart TVs and streaming devices, and those are not really debugger friendly, so logging data in console or going through device logs in &lt;code&gt;telnet&lt;/code&gt; is sometimes the only way to debug. That aside, you should learn how to use the debugger, as it can save you with more complex bugs.&lt;/p&gt;
&lt;p&gt;The simplest way to debug, at least in terms of tooling, is by reading the code you wrote. After that, use the &lt;code&gt;console.log&lt;/code&gt;s, and if even that doesn&apos;t pinpoint the problem, switch to the debugger and good luck.&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Don&apos;t write a comment just for the sake of it. Write comments only for stuff that needs explanation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We all (hopefully) know how important proper documentation and reference material is to a successful software project. Without good docs, a particular library may be next to impossible to use. Without a reference to how different components and methods work in isolation, let alone examples of how all the different pieces of a project fit together with each other, we are left to interpret the original intention of the author merely by reading the source code, or if we are lucky, reaching for StackOverflow and googling random error messages. If this is an in-house or small project, you are probably entirely screwed (been there).&lt;/p&gt;
&lt;p&gt;This is especially important if you are working with several other fellow devs on the project; think about what the other member of the team is going to think about that hack you wrote when he doesn&apos;t know why is that needed. By keeping knowledge of how the code works and why is there many hacks in it or intentionally making code more complicated than it needs to be, you are just making lives of the everyone working on the same project more harder. And if you are doing this for the sole purpose of assuring your job security, you are just a &lt;strong&gt;&lt;code&gt;censored&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For documenting my projects, I&apos;ve been using &lt;a href=&quot;https://jsdoc.app/&quot;&gt;JSDoc&lt;/a&gt; syntax. JSDoc is a standardized way of writing comments in your code to describe functions, classes, methods, and variables in your codebase. The idea is that we describe how our code works with a few special keywords and formatting conventions, and later we can use a parser to run through all of our commented code and generate beautiful, readable documentation based on the comments we write.&lt;/p&gt;
&lt;p&gt;Here’s a short example of how&apos;s it looking like in practice:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * @desc Represents a book.
 * @param {string} title - The title of the book.
 * @param {string} author - The author of the book.
 */
function Book(title, author) {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some of this stuff can be replaced with Typescript types, but even with that, for more complex functions, it&apos;s helpful if we have a good explanation of what it is doing and why do we need it to do that.&lt;/p&gt;
&lt;p&gt;Also, &lt;em&gt;every and one hack in your code should be documented&lt;/em&gt;, believe me, future you are going to be thankful for that.&lt;/p&gt;
&lt;p&gt;And for the end, if you already haven&apos;t, please read &lt;a href=&quot;https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882&quot;&gt;Clean-Code&lt;/a&gt; by Robert C. Martin. Writing clean and readable code is a skill on its own.&lt;/p&gt;
&lt;h2&gt;Learn Javascript&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;But seriously&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Jumping on a Js framework or using a &lt;a href=&quot;https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore&quot;&gt;library&lt;/a&gt; to solve problems you have because you&apos;re not familiar with the core language is going to bite you soon enough. But do not despair, most of us have been there on some level, Js documentation is enormous and, unless you have an excellent memory, there is no way to memorize even a quarter of this &lt;a href=&quot;https://www.ecma-international.org/ecma-262/10.0/index.html#Title&quot;&gt;stuff&lt;/a&gt;. But leveraging &lt;a href=&quot;https://en.wikipedia.org/wiki/Pareto_principle&quot;&gt;Pareto principle&lt;/a&gt; (also known as the 80/20 rule) would be in many cases just enough. Learn how is &lt;code&gt;this&lt;/code&gt; working, all possible operations you can do with objects and arrays, that in Js everything is an object, scope rules, async ops, prototypes (you&apos;ll rarely use these, but it&apos;s essential to understand how inheritance in Js works) and coercion scenarios (so you can laugh at people making stupid mistakes by adding numbers to strings or arrays to arrays and then creating posts on Reddit flaming Js).&lt;/p&gt;
&lt;p&gt;There is truth in saying that if you know Javascript good, then all frameworks and tools based on it are going to be much easier to learn. In the end, those are all just Js under the hood.&lt;/p&gt;
&lt;p&gt;I can also recommend reading &lt;a href=&quot;https://github.com/getify/You-Dont-Know-JS&quot;&gt;You Don&apos;t Know JS&lt;/a&gt; book series if you want to dive deep into Js core mechanisms. (I&apos;m rereading it for the 2nd time).&lt;/p&gt;
&lt;h2&gt;Use the latest standards&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Keep up with times&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It can be challenging to keep up with all the things going on in the web development world, especially since the JavaScript language itself has been changing a lot over the last several years. In 2015, TC39, the committee responsible for drafting the ECMAScript specifications decided to move to a yearly model for defining new standards, where new features would be added as they were approved, rather than drafting complete planned out specs that would only be finalized when all features were ready. As a result, we have ES6 - ES10 specifications which have changed the language a lot, and in a better way. Each of these specifications includes a set of new features/improvements integrated into Javascript, nullifying the need for cumbersome libraries or tools so that you can work with Js and not pull your hair out.&lt;/p&gt;
&lt;p&gt;If you want to get a quick overview of the features being considered for future releases, the best place to look is the &lt;a href=&quot;https://github.com/tc39/proposals&quot;&gt;TC39 proposals repo on Github&lt;/a&gt;. Proposals go through a 4 stage process, where stage 1 can best be understood as a cool “idea,” and stage 4 is “confirmed for the next ECMAScript release.” There is a lot of &lt;a href=&quot;https://github.com/tc39/proposals/blob/master/finished-proposals.md&quot;&gt;cool stuff&lt;/a&gt; coming with ES10 :)&lt;/p&gt;
&lt;p&gt;If you’re interested in keeping up with new proposals but want somebody to walk you through them, I recommend subscribing to Axel Rauschmayer’s &lt;a href=&quot;https://2ality.com/&quot;&gt;2ality&lt;/a&gt; blog. But if you are more of a social interaction person, probably the easiest way to keep up with language evolution is to follow the people who are shaping and teaching the new language features: &lt;a href=&quot;https://twitter.com/TC39&quot;&gt;@TC39&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/sebmarkbage&quot;&gt;Sebastian Markbåge&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/mathias&quot;&gt;Mathias Bynens&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/littledan&quot;&gt;Daniel Ehrenberg&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/bitandbang&quot;&gt;Tierney Cyren&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/rauschma&quot;&gt;Axel Rauschmayer&lt;/a&gt; and probably a lot of other people I forgot.&lt;/p&gt;
&lt;p&gt;Babel has implemented almost all of the higher stage proposals on the TC39 list, and you can try them out in the &lt;a href=&quot;https://babeljs.io/repl/#?babili=false&amp;amp;evaluate=true&amp;amp;lineWrap=false&amp;amp;presets=es2015%2Creact%2Cstage-2&amp;amp;targets=&amp;amp;browsers=&amp;amp;builtIns=false&amp;amp;debug=false&amp;amp;code_lz=Q&quot;&gt;Babel REPL&lt;/a&gt; or by setting up a small project that loads in Babel with the appropriate plugins installed. Also, ff you aren’t familiar with ES6 yet, Babel has a &lt;a href=&quot;https://babeljs.io/docs/en/learn&quot;&gt;excellent summary&lt;/a&gt; of its most essential features.&lt;/p&gt;
&lt;h2&gt;Typescript&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Types good, boilerplate code bad&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;JavaScript is a loosely typed language, also known as a dynamically typed language, which means that it is flexible and does type checking on run-time rather than compile time. This means you can create a variable that starts as string type, but then change it to a number type, etc.
For many people that have started programming in C or Java, this is horrifying (ergo coercion memes on Reddit), as those languages are pretty strict with types and require a full definition of data type or interface for a constant. Anyway, there’s a lot to love about static types: static types can be beneficial to help document functions, clarify usage, and reduce cognitive overhead. But, IMO, there’s a lot to love about dynamic types as well.&lt;/p&gt;
&lt;p&gt;So, there comes Typescript. Typescript is Javascript, with an extra layer that adds static typing tools and capabilities to your Javascript code. As you’re developing an application, you will be writing Typescript code, which then gets compiled to plain JavaScript for the browser to understand. It &lt;strong&gt;can fix some of the issues&lt;/strong&gt; dynamically typed languages have, a big plus is if you use one of the TS supported editors (VS Code, Atom, Webstorm) which can provide the excellent dev experience and boost your productivity. That aside, I hate a boilerplate code that comes with TS. A few TS projects I&apos;ve been working with have at least 30-40% more lines of code just because of TS types, interfaces and enums. Errors can be cryptic sometimes, and debugging type issues can get on a nerve real quick. Merged types and some advanced type definitions can be tiring to read and understand sometimes.&lt;/p&gt;
&lt;p&gt;There is a &lt;a href=&quot;https://medium.com/javascript-scene/the-typescript-tax-132ff4cb175b&quot;&gt;excellent article&lt;/a&gt; by Eric Elliott about Typescript&apos;s bad and good sides if you want to read more. Still, my overall opinion of TS is positive, just because whenever I go back to read the code, I can (almost always!) understand immediately and thoroughly what each type of variable is, what this function returns, whether this array has been changed throughout the program, etc. That&apos;s a lot of saved time and minimized the number of redundant operations to check the type and structure of the data.&lt;/p&gt;
&lt;h2&gt;Code testing, integration, and delivery&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Give a developer a tedious task, and they&apos;ll find a way to automate it&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Probably most of us here are familiar with tools as Webpack, Gulp, Grunt, lint-staged. Even Prettier and Eslint are a kind of automation tool. The less time we spend on menial or repeating tasks, the more time we have to focus on the actual problems.&lt;/p&gt;
&lt;p&gt;Few developers get excited over the idea of writing tests for their code. Especially when there is a pressure to finish new features as fast as possible, writing test code that doesn’t directly contribute to the progress of the project can be annoying. When the project is small and you can test a few available features manually this might be fine, but once the project starts to grow manual testing is time-consuming and horribly inefficient.&lt;/p&gt;
&lt;p&gt;Investing in testing upfront is one of the best investments you can make on your project. It is what allows you to write a feature, not touch it for weeks, come back, see it is passing all its tests, and have a level of confidence that everything is good in the world.&lt;/p&gt;
&lt;p&gt;I&apos;ve been using mostly &lt;a href=&quot;https://github.com/facebook/jest&quot;&gt;Jest&lt;/a&gt; for my tests, but I&apos;ve heard good things about &lt;a href=&quot;https://github.com/ericelliott/riteway&quot;&gt;Riteway&lt;/a&gt;. Testing React components have gotten more difficult since the introduction of the hooks, Enzyme is having a hard time so I&apos;m not sure if I can recommend it anymore, &lt;a href=&quot;https://github.com/testing-library/react-testing-library&quot;&gt;react-testing-library&lt;/a&gt; might be a better choice for now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Continuous integration&lt;/strong&gt; is the software development practice of frequently integrating changes to a shared code repository. For every integration, automatic formatting and testing should be done. This gives the developers a quick feedback cycle for determining potential conflicts in commits while also allowing to frequently merge new updates to an application.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Continuous delivery&lt;/strong&gt; works in conjunction with CI to take the tested and built application that results from the CI process and deploy (or deliver) it to the intended infrastructure. With CD, teams can push new code to production every day or even hourly and get quick feedback on what users care about.&lt;/p&gt;
&lt;p&gt;A lot can be told about how to write tests and how to configure CI/CD pipeline but that would be a whole post on its own. There is no golden rule for how to write perfect tests but making sure that you at least write them and are trying to get ~80% coverage with a combination of unit, integration, and e2e tests should lead to clean and confident code.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;I always struggle with summaries (same thing with prefaces). For me, it&apos;s usually hard to start writing a post, after that, I can go on and on, same with deciding how to end it :smile: I still feel like I haven&apos;t wrote enough about all topics mentioned, so feel free to comment if you have any questions.&lt;/p&gt;
&lt;p&gt;Bear in mind that this is a half rant and half commentary to myself, after several years working with Js. There’s a whole class of internet comments that can be summarised as “I disagree, and that makes me ANGRY, here&apos;s a downvote”, which is a pity, because when two reasonable people disagree, there’s very often something interesting going on.&lt;/p&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;
&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@adigold1?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Adi Goldstein&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/code-foo?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Web dev</category></item><item><title>Making New Languages Click with LLMs</title><link>https://darkotasevski.dev/writing/making-new-languages-click-ai/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/making-new-languages-click-ai/</guid><description>The learning curve doesn&apos;t have to be a wall. Here&apos;s how I turn AI into my coding buddy when I&apos;m in unfamiliar territory.</description><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I jump into a new language or framework, there&apos;s always that awkward period where I know what I want to do, but I don&apos;t yet know the way to do it. The docs help. Searching helps. But lately, I&apos;ve found AI, specifically large language models, to be a surprisingly effective shortcut for getting up to speed.&lt;/p&gt;
&lt;p&gt;That&apos;s been especially true recently as I&apos;ve been learning Rust from scratch and re-learning Ruby after years away. In both cases, AI has helped me bridge the gap between knowing the outcome I want and understanding the idiomatic way to get there.&lt;/p&gt;
&lt;p&gt;I&apos;m not talking about letting AI write whole features for me and calling it a day. I use it like I&apos;d use an experienced coworker: to explain unfamiliar syntax, walk me through tricky logic, or point out patterns I wouldn&apos;t have spotted. Here&apos;s how that looks in practice.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Prime It Like a Person&lt;/h2&gt;
&lt;p&gt;The most significant shift for me was realizing you can&apos;t treat AI just like a search box. It works better when you talk to it like it&apos;s a person with no memory of your project, because, spoiler, it &lt;em&gt;is&lt;/em&gt; like that.&lt;/p&gt;
&lt;p&gt;I start by giving it a role (&quot;You&apos;re a Rust mentor helping me understand lifetime annotations in this function...&quot;), set some rules, and provide the snippet or context I&apos;m working with. The more specific I am, the better it answers. This is basically the &quot;prompt library&quot; habit—keeping go-to instructions that get me 80% of the way there for common questions.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fn longest&amp;lt;&apos;a&amp;gt;(x: &amp;amp;&apos;a str, y: &amp;amp;&apos;a str) -&amp;gt; &amp;amp;&apos;a str {
    if x.len() &amp;gt; y.len() { x } else { y }
}

// My prompt to AI
&quot;You’re a Rust mentor. Explain exactly why we need `&apos;a` here
and how the compiler would complain if I removed it.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That way, I get an answer that goes straight to the nuance instead of a generic &quot;this is a function&quot; lecture.&lt;/p&gt;
&lt;p&gt;I wrote about the &lt;a href=&quot;https://dev.to/puritanic/thinking-clearly-with-llms-mental-models-and-cognitive-pitfalls-in-prompt-engineering-3dmm&quot;&gt;mental models&lt;/a&gt; behind prompting and some of the common traps developers (and humans) fall into, and how to avoid them.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Use It to Decode Syntax Quickly&lt;/h2&gt;
&lt;p&gt;When I hit syntax sugar I don&apos;t recognize, I paste in the snippet and ask for a breakdown. I don&apos;t just want &lt;em&gt;what&lt;/em&gt; it does, I want the &lt;em&gt;&quot;why&quot;&lt;/em&gt; behind the choice.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def create_user(name:, email:, admin: false)
  { name: name, email: email, admin: admin }
end

create_user(name: &quot;Alice&quot;, email: &quot;alice@example.com&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I weren&apos;t used to Ruby&apos;s keyword arguments, I&apos;d feed just this snippet to AI and say: &quot;Walk me through what&apos;s happening here and why &lt;code&gt;admin&lt;/code&gt; has a default value.&quot;&lt;/p&gt;
&lt;p&gt;The key is to focus on small, concrete chunks of code. If I throw in a 200-line file, the explanation gets vague. But if I isolate a pattern and ask for a line-by-line walk-through, I actually learn something I can reuse.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Pinpoint Where I&apos;m Stuck in the Logic&lt;/h2&gt;
&lt;p&gt;Sometimes I understand most of the code, but one function call or return type throws me off. This is where I frame my prompt like I&apos;m talking to a colleague: &quot;I get it up to here, but then I&apos;m lost, can you tell me what&apos;s happening next?&quot;&lt;/p&gt;
&lt;p&gt;By declaring what I &lt;em&gt;do&lt;/em&gt; know, I make it easier for the model to skip the obvious and focus on the missing piece. That&apos;s saved me from drowning in over-explained basics.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Ask for a Refactor, Not a Rewrite&lt;/h2&gt;
&lt;p&gt;I&apos;ll sometimes ask AI to refactor a snippet, especially if I suspect it&apos;s inefficient or violating a language-specific convention. For example, removing an &lt;code&gt;await&lt;/code&gt; from a &lt;code&gt;for&lt;/code&gt; loop in Javascript, without breaking the logic.&lt;/p&gt;
&lt;p&gt;I treat these as learning moments. I compare its suggestion with my own approach, figure out why it&apos;s better (or worse), and adjust. This way, I&apos;m not blindly adopting AI output.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Generate Examples to Learn From&lt;/h2&gt;
&lt;p&gt;If I&apos;m brand new to a language, I might have AI generate a function from a detailed description I give it. Then I dissect the result, asking follow-up questions about why it chose certain patterns, or whether there&apos;s a more idiomatic way.&lt;/p&gt;
&lt;p&gt;This isn&apos;t about having AI &quot;do the work.&quot; It&apos;s about creating a tangible example I can explore, question, and improve. Done right, it&apos;s like having an interactive coding textbook.&lt;/p&gt;
&lt;p&gt;For Example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Write a Rust function that takes a list of words and returns only those longer than five letters, in alphabetical order.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;AI output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fn filter_words(words: Vec&amp;lt;&amp;amp;str&amp;gt;) -&amp;gt; Vec&amp;lt;&amp;amp;str&amp;gt; {
    let mut filtered: Vec&amp;lt;&amp;amp;str&amp;gt; = words.into_iter()
        .filter(|w| w.len() &amp;gt; 5)
        .collect();
    filtered.sort();
    filtered
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I dissect it: Why &lt;code&gt;into_iter&lt;/code&gt; instead of &lt;code&gt;iter&lt;/code&gt;? Could it be more idiomatic with method chaining? What happens if I use &lt;code&gt;sort_unstable&lt;/code&gt;?&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Bottom Line&lt;/h2&gt;
&lt;p&gt;AI isn&apos;t my teacher, it&apos;s my accelerator. It&apos;s not replacing documentation, real code reviews, or the need to tinker on my own. But when I use it deliberately, it helps me move through the clumsy &quot;getting familiar&quot; phase much faster.&lt;/p&gt;
&lt;p&gt;If you&apos;re learning a new language, try using AI the way you&apos;d lean on a helpful peer: ask focused questions, give it the right context, and use its answers as a springboard for your own understanding.&lt;/p&gt;
</content:encoded><category>Software Engineering</category></item><item><title>Parkinson’s Law in Software Engineering</title><link>https://darkotasevski.dev/writing/parkinsons-law-in-se/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/parkinsons-law-in-se/</guid><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you&apos;ve ever finished a task at the very last minute, despite having more than enough time, you&apos;ve already experienced Parkinson&apos;s Law. It&apos;s the idea that &lt;em&gt;&quot;work expands to fill the time available for its completion.&quot;&lt;/em&gt; Cyril Northcote Parkinson first described the concept in &lt;em&gt;&lt;a href=&quot;https://web.archive.org/web/20180705215319/https://www.economist.com/news/1955/11/19/parkinsons-law&quot;&gt;Economist&lt;/a&gt;&lt;/em&gt; in 1955, and it has since become a well-cited principle in productivity studies.&lt;/p&gt;
&lt;p&gt;If you give yourself a week to do a task, you&apos;ll take a week. If you give yourself four months, you&apos;ll still find a way to make it last four months. And in software engineering, where deadlines, sprints, and priorities shift constantly, this principle shows up everywhere.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;How I&apos;ve Seen It Happen First-Hand&lt;/h2&gt;
&lt;h3&gt;The API&lt;/h3&gt;
&lt;p&gt;At one company, we started working on an API feature that should have taken two or three weeks (at most). Straightforward stuff, wire up a couple of endpoints, add tests, ship it. Instead, it dragged on for nearly four months. Why? Every week we&apos;d add &quot;one more thing&quot;, debating naming conventions, attaching edge-case functionality, or rewriting parts to make it &quot;future proof™.&quot; The work didn&apos;t get harder; we just let the available time invite more work into the room.&lt;/p&gt;
&lt;h3&gt;The Feature Flags&lt;/h3&gt;
&lt;p&gt;Another time, we were tasked with removing a batch of old feature flags. On paper, this was cleanup work. A day or two, maybe a week. But because there wasn&apos;t a sharp deadline, it ballooned. We delayed, dependencies shifted, and soon those flags were entangled with new features. What could have been a tidy sweep turned into a gnarly refactor. The longer we waited, the harder it got.&lt;/p&gt;
&lt;h3&gt;Flaky Tests&lt;/h3&gt;
&lt;p&gt;At one of my previous companies, flaky tests were a recurring pain point. Initially, they should&apos;ve been quick to fix, a 30-minute job here, an hour there. But when they got left for &quot;later&quot;, suddenly they piled up into a backlog that took a whole week just for categorization. It took months to fix them all and bring the CI back to a stable state. Parkinson&apos;s Law doesn&apos;t just stretch individual tasks, it compounds when you defer maintenance.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Why Engineers Fall Into the Trap&lt;/h2&gt;
&lt;p&gt;The more time we &lt;em&gt;think&lt;/em&gt; we have, the more we delay starting. By the time the deadline finally feels real, we&apos;re scrambling to finish anyway. Sound familiar?&lt;/p&gt;
&lt;h3&gt;Procrastination Disguised as Preparation&lt;/h3&gt;
&lt;p&gt;Without pressure, it&apos;s easy to tell yourself you&apos;re &quot;planning&quot; or &quot;polishing.&quot; I&apos;ve spent afternoons renaming variables and rewriting the code to be more performant and nicer when the real fix was already done an hour earlier.&lt;/p&gt;
&lt;h3&gt;Overengineering by Default&lt;/h3&gt;
&lt;p&gt;When there&apos;s time, scope expands. A hotfix turns into a refactor. A CRUD API becomes a new framework. Sometimes we call it &quot;doing it right&quot;, but often it&apos;s Parkinson&apos;s Law whispering in the background. This overlaps with the &lt;a href=&quot;https://en.wikipedia.org/wiki/Law_of_triviality&quot;&gt;Law of Triviality&lt;/a&gt;, where teams spend disproportionate effort on small details while bigger issues wait.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Management Isn&apos;t Immune Either&lt;/h2&gt;
&lt;p&gt;It&apos;s not just engineers who fall prey to Parkinson&apos;s Law. Managers fall into the same trap when planing sprints, setting timelines, or scheduling meetings. The same rule applies: the more time and space you give, the more things tend to expand and drag out.&lt;/p&gt;
&lt;h3&gt;Sprints Expand to Fit&lt;/h3&gt;
&lt;p&gt;If you give a team a two-week sprint, the work will stretch to fill two weeks. Even tasks that could be done in three days get scheduled and discussed until they spill over.&lt;/p&gt;
&lt;h3&gt;Meetings&lt;/h3&gt;
&lt;p&gt;If the calendar says 1 hour, the meeting will last 1 hour. I&apos;ve seen 10-minute discussions dragged out just because the slot was there.&lt;/p&gt;
&lt;h3&gt;Big Project Deadlines&lt;/h3&gt;
&lt;p&gt;Give a team a vague &quot;end of quarter&quot; target, and you&apos;ll see Parkinson&apos;s Law bloom into endless scope creep, extra reviews, and &quot;one last tweak.&quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Turning Parkinson&apos;s Law to Your Advantage&lt;/h2&gt;
&lt;p&gt;The trick isn&apos;t fighting it, it&apos;s using it.&lt;/p&gt;
&lt;h3&gt;Time-boxing&lt;/h3&gt;
&lt;p&gt;At another company, I&apos;ve seen how time-boxing can save a messy discovery call. A concise 25-minute agenda forces clarity that an open-ended meeting can never deliver. The same applies to coding: two hours for a spike means you actually spike instead of redesigning the system. Agile teams rely heavily on this principle, framing all work within sprints or fixed blocks (&lt;a href=&quot;https://www.atlassian.com/agile/project-management/timeboxing&quot;&gt;Atlassian on timeboxing&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;Artificial Deadlines&lt;/h3&gt;
&lt;p&gt;When I know a task could balloon, I try to give myself an earlier &quot;fake&quot; deadline. For example, I&apos;ll aim to finish a feature by Wednesday, even if the sprint closes Friday. That gap is my buffer for polish, rather than panic.&lt;/p&gt;
&lt;h3&gt;Smaller Work Units&lt;/h3&gt;
&lt;p&gt;Breaking a large ticket into smaller ones makes Parkinson&apos;s Law work for you. Each unit has a tighter deadline, which keeps the scope from sprawling.&lt;/p&gt;
&lt;h3&gt;Hackathon Constraints&lt;/h3&gt;
&lt;p&gt;I&apos;ve seen hackathons produce working prototypes in 48 hours that would have taken months in a normal sprint. Why? The constraints kill off overthinking. You don&apos;t have the luxury to debate naming conventions, you just ship.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Trade-Offs&lt;/h2&gt;
&lt;p&gt;Of course, speed isn&apos;t always better. Some work &lt;em&gt;needs&lt;/em&gt; breathing room. Designing a distributed architecture or building core SDK APIs can&apos;t be rushed without long-term pain. The key is knowing when Parkinson&apos;s Law is helping you focus, and when it&apos;s luring you into cutting corners.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Another common trap&lt;/strong&gt;: new managers who read about Parkinson&apos;s Law sometimes take it too literally. They assume that if shorter deadlines make people more efficient, then &lt;em&gt;unrealistic&lt;/em&gt; deadlines must make them even faster. In practice, this backfires. Instead of encouraging focus, it creates a culture of constant firefighting, technical debt, and burnout. Deadlines should constrain scope, not crush the team. The art is in balancing enough pressure to avoid bloat, without crossing into &quot;death march&quot; territory (&lt;a href=&quot;https://asana.com/resources/parkinsons-law&quot;&gt;Asana&apos;s guide&lt;/a&gt;).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Parkinson&apos;s Law isn&apos;t just a productivity idea, it&apos;s a pattern I&apos;ve seen in every engineering role I&apos;ve had. Whether it&apos;s a sprint&apos;s work stretching over multiple sprints, a cleanup task turning into a refactor, or a project ballooning because &quot;we have time,&quot; the principle holds: work expands to fill the space you give it.&lt;/p&gt;
&lt;p&gt;The good news? With the right constraints, time-boxing, artificial deadlines, smaller tasks, you can flip Parkinson&apos;s Law into an advantage. The next time you&apos;re estimating a feature or planning a sprint, ask yourself: &lt;em&gt;how much time does this really deserve?&lt;/em&gt; You might find that less time gives you better results.&lt;/p&gt;
</content:encoded><category>Management</category></item><item><title>Add PostCSS to Create-React-App</title><link>https://darkotasevski.dev/writing/postcss-cra/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/postcss-cra/</guid><description>Learn a bit about PostCSS and how to add it to CreateReactApp. Without ejecting!</description><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Without ejecting!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;A small introduction to PostCSS&lt;/h2&gt;
&lt;p&gt;Many of you are spending your time working with CSS, so I guess you are familiar with preprocessors such as Less, Sass, and Stylus. These tools are a vital part of the web development ecosystem nowadays, and I cannot imagine writing styles for a website without using features like nesting, variables, or mixins as it can be cumbersome, awkward and sometimes less intuitive.&lt;/p&gt;
&lt;p&gt;But, there is a couple of problems with traditional preprocessors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They don&apos;t follow CSS standards.
You might say that each of the preprocessors has become a standard of its own. Regrettably, they don’t aim at being compatible with the W3C standards, which means that they cannot you cant use their features as polyfills for early testing of the newer W3C standards.&lt;/li&gt;
&lt;li&gt;They are not extendable.
Whichever preprocessor you choose, you are limited to the set of features that it provides. If you need anything on top of that, you’ll need to add it as a separate step in your build process. If you feel like writing your extension, you’re on your own. Good luck with that.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is where PostCSS comes in.&lt;/p&gt;
&lt;p&gt;In a nutshell, PostCSS does the same thing as LESS/SASS/SCSS...
But enhance it with a few plugins, and it can do much more.
&lt;em&gt;PostCSS is much like Babel for JavaScript.&lt;/em&gt;
It parses your CSS, using Javascript under the hood, turning it into the raw AST (abstract syntax tree) and then performs transformations to the CSS that today&apos;s browsers will understand and render.
And a bonus is that JavaScript can transform our styles much more quickly than other processors.&lt;/p&gt;
&lt;p&gt;Enough about PostCSS and let&apos;s focus on the purpose of this post.
After some Googling, I&apos;ve found out that for PostCss to work, you had to eject CRA to edit the underlying Webpack configuration, which deals with all the necessary loaders for different file types, which I&apos;ve found as a bit of a drastic measure. In the end, after a bit of trial and error hacking together different solutions, I got it working. Here’s how!&lt;/p&gt;
&lt;p&gt;You can find the final code here: &lt;a href=&quot;https://github.com/Puritanic/CRA-feat-PostCSS&quot;&gt;https://github.com/Puritanic/CRA-feat-PostCSS&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Edit: It seems that my google foo isn&apos;t good as I thought it was. Here&apos;s an &lt;a href=&quot;https://github.com/csstools/react-app-rewire-postcss&quot;&gt;alternate method&lt;/a&gt; for extending CRA with PostCSS created by @jonathantneal&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;First, create a new React app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;create-react-app postcss-cra
cd postcss-cra
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And install postcss-cli and a few basic plugins&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add --dev autoprefixer postcss-nested postcss-cli npm-run-all 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, at the root of the project, you need to create a file called &lt;code&gt;postcss.config.js&lt;/code&gt; and add this code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
    plugins: [
        require(&apos;postcss-nested&apos;),
        require(&apos;autoprefixer&apos;),
    ],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Almost there!
Now all that is left is to edit the scripts in &lt;code&gt;package.json&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    &quot;scripts&quot;: {
        &quot;build:css&quot;: &quot;postcss src/styles/main.css -o src/index.css&quot;,
        &quot;watch:css&quot;: &quot;postcss src/styles/main.css -o src/index.css -w&quot;,
        &quot;start&quot;: &quot;npm-run-all -p watch:css start-js&quot;,
        &quot;start-js&quot;: &quot;react-scripts start&quot;,
        &quot;build-js&quot;: &quot;react-scripts build&quot;,
        &quot;build&quot;: &quot;npm-run-all build:css build-js&quot;,
        &quot;test&quot;: &quot;react-scripts test --env=jsdom&quot;,
        &quot;eject&quot;: &quot;react-scripts eject&quot;
        },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a styles folder inside &lt;code&gt;src&lt;/code&gt; where our CSS will live:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir src/styles
mv src/App.css src/styles/main.css  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And to test postcss, let&apos;s modify default CRA styles a bit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* src/styles/main.css */
.App {
  text-align: center;

    &amp;amp;-logo {
        animation: App-logo-spin infinite 20s linear;
        height: 80px;
    }

    &amp;amp;-header {
        background-color: #222;
        height: 150px;
        padding: 20px;
        color: white;
    }

    &amp;amp;-title {
        font-size: 1.5em;
    }

    &amp;amp;-intro {
        font-size: large; 
    }
}
@keyframes App-logo-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Don&apos;t forget to remove &lt;code&gt;import &apos;./App.css&apos;;&apos;&lt;/code&gt; from App.js, as we&apos;ve moved that file to styles and renamed it to &lt;code&gt;main.css&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Moment of truth! Now run: &lt;code&gt;yarn run start&lt;/code&gt;.
In the browser, you should see almost the same styles that CRA by default has. Now let&apos;s see output &lt;code&gt;index.css&lt;/code&gt; file in &lt;code&gt;src/&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.App {
    text-align: center;
    display: flex;
    flex-direction: column;
}

.App-logo {
        -webkit-animation: App-logo-spin infinite 20s linear;
                animation: App-logo-spin infinite 20s linear;
        height: 80px;
    }

.App-header {
        background-color: #222;
        height: 150px;
        padding: 20px;
        color: white;
    }

.App-title {
        font-size: 1.5em;
    }

.App-intro {
        font-size: large;
    }

@-webkit-keyframes App-logo-spin {
    from {
        -webkit-transform: rotate(0deg);
                transform: rotate(0deg);
    }
    to {
        -webkit-transform: rotate(360deg);
                transform: rotate(360deg);
    }
}

@keyframes App-logo-spin {
    from {
        -webkit-transform: rotate(0deg);
                transform: rotate(0deg);
    }
    to {
        -webkit-transform: rotate(360deg);
                transform: rotate(360deg);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voila! As you can see, our styles are automatically auto-vendor-prefixed for better support, and our nested code is transformed to code that browser can understand.&lt;/p&gt;
&lt;p&gt;Now let&apos;s do something more advanced:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add -D postcss-import postcss-preset-env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, let&apos;s add those new plugins to &lt;code&gt;postcss.config.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
    plugins: [
        require(&apos;postcss-import&apos;),
        require(&apos;postcss-preset-env&apos;)({
            stage: 1,
        }),
        require(&apos;postcss-nested&apos;),
        require(&apos;autoprefixer&apos;),
    ],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, create a test files inside styles folder:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;touch src/styles/styles.css src/styles/styles1.css
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Move everything from &lt;code&gt;src/styles/main.css&lt;/code&gt; to styles.css and add this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@import &apos;styles.css&apos;;
@import &apos;styles1.css&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, in &lt;code&gt;src/styles/styles1.css&lt;/code&gt; add this weird mumbo-jumbo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@custom-media --viewport-medium (width &amp;lt;= 50rem);
@custom-selector :--heading h1, h2, h3, h4, h5, h6;

:root {
    --fontSize: 1rem;
    --mainColor: #12345678;
    --secondaryColor: lab(32.5 38.5 -47.6 / 90%);
}

html {
    overflow: hidden auto;
}

@media (--viewport-medium) {
    body {
        color: var(--mainColor);
        font-family: system-ui;
        font-size: var(--fontSize);
        line-height: calc(var(--fontSize) * 1.5);
        overflow-wrap: break-word;
        padding-inline: calc(var(--fontSize) / 2 + 1px);
    }
}

:--heading {
    margin-block: 0;
}

.App-header:matches(header, .main) {
    background-image: image-set(&apos;./img/background.jpg&apos; 1x, &apos;./img/background-2x.jpg&apos; 2x);
    background-size: contain;
}

a {
    color: rebeccapurple;

    &amp;amp;:hover {
        color: color-mod(var(--secondaryColor) b(15%) a(75%));
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now restart CRA server. Everything should work as intended, except for several (obligatory) cats showing in the header now :D&lt;/p&gt;
&lt;p&gt;This is just a scratch on the surface, PostCSS have enormous power in its plugins, and have a great community around it. What&apos;s the best is that the most of PostCSS plugins are using stuff that will be used as native CSS syntax in future, so your code is future-proof. In the end, I&apos;m enjoying using it, and that&apos;s what matters, and I hope that you will too. Thanks for reading!&lt;/p&gt;
&lt;p&gt;Some resources:
&lt;a href=&quot;https://github.com/postcss/postcss&quot;&gt;Official PostCSS Github Repo&lt;/a&gt; and &lt;a href=&quot;https://postcss.org/&quot;&gt;Page&lt;/a&gt;
&lt;a href=&quot;https://preset-env.cssdb.org/&quot;&gt;PostCSS-preset-env playground&lt;/a&gt;
&lt;a href=&quot;https://github.com/jdrgomes/awesome-postcss&quot;&gt;List of awesome PostCSS plugins&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Web dev</category></item><item><title>I&apos;ve read... A Mathematician&apos;s Lament</title><link>https://darkotasevski.dev/writing/read-mathematicians-lament/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/read-mathematicians-lament/</guid><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;If you want to build a boat, don&apos;t order people to gather wood and assign tasks and work, but instead teach people to long for the boundless immensity of the sea.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A Mathematician&apos;s Lament is more of an extended essay than a book - one man&apos;s problems with mathematics education without a viable solution. While I agree with him that the current state of mathematics in schools and universities is a travesty, the kind of &quot;solution&quot; he recommends is not the real solution. We all know what most of us thought about the math (and many other fields) in ES and HS, and leaving it all to the student is not an excellent solution.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Mathematics is the art of explanation. If you deny students the opportunity to engage in this activity - to pose their own problems, to make their own conjectures and discoveries, to be wrong, to be creatively frustrated, to have an inspiration, and to cobble together their own explanations and proofs - you deny them mathematics itself.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think he argues that mathematics education gets tripped up in unnecessary formalism and syntax before conceptually interesting problems are tackled. The whole thing is defended with the ridiculous &quot;you might need this someday&quot; pragmatism that children will instantly tune out - is a sound one. It would be great if every elementary school teacher were the kind of engaged leader capable of putting their students to work on an exciting geometry or abstract algebra problem and wandering around not to give answers but to provide the occasional hint. But, unfortunately, I don&apos;t see how this will provide anyone with well-rounded mathematics education.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Don&apos;t get me wrong! I liked the premise of his essay. I would love that my professors had just a pinch of Lockhart&apos;s teaching spirit; it would make studying math much enjoyable. But, while I can see how it would make learning math so much more fun, it isn&apos;t practical. I think it could be accomplished in a small setting, but let&apos;s face it, the world doesn&apos;t allow for exploration. You have to master what they want you to master, troubling as that may be.&lt;/p&gt;
</content:encoded><category>Software Engineering</category></item><item><title>I&apos;ve read... The Pragmatic Programmer</title><link>https://darkotasevski.dev/writing/read-pragmatic-programmer/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/read-pragmatic-programmer/</guid><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Truly a classic. It&apos;s definitely a must-read book for programmers and even people managing programmers. Initially released in 1999, The Pragmatic Programmer is a book about becoming a Pragmatic Programmer - programmer that&apos;s a true professional in their craft. And, even though it was published twenty years ago, it&apos;s fascinating to see the struggles we still face day in and day out discussed even then.&lt;/p&gt;
&lt;p&gt;When I first started reading this book, I&apos;ve expected a lot of technical details and lessons, which is probably one of the reasons why I&apos;ve been avoiding this book so far. I mean, it&apos;s &lt;strong&gt;twenty&lt;/strong&gt; years old, and in today&apos;s pace of technology, technical details do not stay up to date very long. But instead, this book concerns the most challenging parts of the programmer&apos;s career: writing scalable and maintainable software and scaling themselves as professionals.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;There are code snippets&lt;/em&gt;, but the authors are aware of the code and techniques getting out of date in a matter of years, so the book is not focusing on them too much.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In general, there are not many surprises in what the book&apos;s authors are trying to deliver. Any programmer who cares about their craft, has no fear of change, and already has a few years of experience will already know many themes explored in this book. In my opinion, most programmers are aware of the guidelines this book is preaching, but they are also quick on finding excuses to ignore them.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Pragmatic Programmer&lt;/em&gt; centers on how to use software to solve problems effectively and how to grow as the developer pragmatically; not just how to be a good programmer, but also how to solve the complex issues that surround coding, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Writing clean code through &lt;a href=&quot;https://en.wikipedia.org/wiki/Don%27t_repeat_yourself&quot;&gt;DRY&lt;/a&gt; (Don&apos;t repeat yourself) and &lt;a href=&quot;https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it&quot;&gt;YAGNI&lt;/a&gt; (You aren&apos;t gonna need it)&lt;/li&gt;
&lt;li&gt;How to estimate the software delivery.&lt;/li&gt;
&lt;li&gt;How to institute change when others are hesitant.&lt;/li&gt;
&lt;li&gt;How to combat stagnancy as a developer.&lt;/li&gt;
&lt;li&gt;How to make the software processes resilient and efficient through automation and testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The examples and explanations are not abstract or far-fetched but are somewhat real-world applications of things you could see in the industry (though some stuff is outdated).&lt;/p&gt;
&lt;p&gt;Some of the significant points of the book that I&apos;m going to cover are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We should take &lt;strong&gt;responsibility&lt;/strong&gt; for our code and decisions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not leave broken windows unrepaired.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Think critically&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Know your tools&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Program and refactor deliberately&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use Version Control&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test your code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automate all the things&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hunt and Thomas are stating that the last three things from the list above are the essence of the Pragmatic Starter Kit, and that should be the three legs that support every project.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Responsibility&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When you have responsibility for something, you should prepare yourself to be held accountable. If you make mistakes and cannot fulfill those responsibilities, you have to make up for them and find a solution. Don&apos;t give excuses and play the &lt;a href=&quot;https://en.wikipedia.org/wiki/Cover_your_ass&quot;&gt;finger-pointing game&lt;/a&gt;. When you make a mistake (to err is human) or an error in judgment, admit it honestly and try to offer alternatives. Don&apos;t blame all the problems on a vendor, a programming language, management, or your coworkers. Any of these may play a role, but it is up to you to provide solutions, not excuses.&lt;/p&gt;
&lt;p&gt;Don&apos;t approach anyone to tell them that something couldn&apos;t be done before you are entirely sure that that&apos;s correct. Additionally, don&apos;t just say you can&apos;t do it, like it&apos;s the end of the story. Instead, provide options and explain what &lt;em&gt;can&lt;/em&gt; be done to salvage the situation.&lt;/p&gt;
&lt;p&gt;As the authors of the book say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Try to flush out the lame excuses before voicing them aloud. Does your excuse sound reasonable or stupid? How&apos;s it going to sound to your boss? (...) Try to flush out the lame excuses before voicing them aloud. If you must, tell your cat first.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Broken windows&lt;/h2&gt;
&lt;p&gt;There is a story about research studying the effect of broken windows on urban areas in the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One broken window, left unrepaired for any substantial length of time, instills in the inhabitants of the building a sense of abandonment—a sense that the powers that be don&apos;t care about the building. So another window gets broken. People start littering. Graffiti appears. Serious structural damage begins. In a relatively short span of time, the building becomes damaged beyond the owner&apos;s desire to fix it, and the sense of abandonment becomes a reality.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We, programmers, have probably seen this in some codebases. We see some broken code and think it&apos;s okay to leave it. We&apos;ll just come back when there is not enough work and fix it. But, unfortunately, this is just the first step to degradation of code quality and serious tech debt. After one broken window, the others will start appearing more frequently, and this is usually the time when developers start looking for a new job, leaving a dumpster fire behind.&lt;/p&gt;
&lt;p&gt;So, (please), &lt;strong&gt;don&apos;t leave &quot;broken windows&quot; (bad designs, wrong decisions, or poor code) unrepaired&lt;/strong&gt;. Fix each one as soon as it is discovered. If there is insufficient time to fix it properly, create a ticket, fix the most offending issue if possible, comment out the code, or leave a screaming comment. Ultimately, you should take action to prevent further damage and show that you&apos;re on top of the situation.&lt;/p&gt;
&lt;p&gt;Do not fall to &lt;a href=&quot;https://en.wikipedia.org/wiki/Bystander_effect&quot;&gt;the Bystander effect&lt;/a&gt;, hoping that other developers will fix the problems. Instead, take the initiative, and be a catalyst for change.&lt;/p&gt;
&lt;h2&gt;Think critically&lt;/h2&gt;
&lt;p&gt;You should think critically about what you read and hear. You need to ensure that your knowledge and opinions are unswayed by either vendor or media hype. Beware of the salesman who insists that their solution provides the only answer; it may or may not apply to you and your project, or they might be just trying to sell you the snake oil.&lt;/p&gt;
&lt;p&gt;Try to ask and think with the following questions when you want to get to the bottom of something:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ask the &quot;Five Whys&quot;&lt;/strong&gt; - Ask a question, and get an answer. Then, dig deeper by asking another &quot;why?&quot; Then, repeat the question as long as it&apos;s reasonable to do. You might be able to get closer to a root cause this way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Who does this benefit?&lt;/strong&gt; - It may sound cynical, but following the money can be a helpful path to analyze.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What&apos;s the context?&lt;/strong&gt; - Everything occurs in its context, which is why &quot;one size fits all&quot; solutions often don&apos;t work. Good questions to consider are &quot;best for who?&quot; What are the prerequisites, what are the consequences, short and long term?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When or Where would this work?&lt;/strong&gt; - Under what circumstances? Don&apos;t stop with first-order thinking (what will happen next), but use second-order thinking: what will happen after that?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Why is this a problem?&lt;/strong&gt; - Is there an underlying model? How does the underlying model work? Do you even have the same problem?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Know your tools&lt;/h2&gt;
&lt;p&gt;This could seem like simple advice, but we are surrounded by a wide range of tools in our daily jobs. I don&apos;t know the ins and outs for each one I&apos;m using, for sure.&lt;/p&gt;
&lt;p&gt;For example, a few days ago, I&apos;ve learned about &lt;a href=&quot;https://johnkary.net/blog/git-add-p-the-most-powerful-git-feature-youre-not-using-yet/&quot;&gt;&lt;em&gt;git add --patch&lt;/em&gt;&lt;/a&gt; functionality that allows us to stage only parts of the changed files.&lt;/p&gt;
&lt;p&gt;While I&apos;m not sure that it would be advisable to learn everything possible about tools we&apos;re using for development, learning about stuff that can make you productive is definitely something we should strive for. For example, pay attention to your daily flow and see what manual actions you are performing most often. Then look if those can be automated or improved somehow.&lt;/p&gt;
&lt;p&gt;The book teaches us that it&apos;s essential to find the proper tools before starting the development. By essential tools, it&apos;s not just the IDE but also the programming language and services. The more you are versed with different technologies, the wider the picture you&apos;ll have.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://marcel.is/quench/&quot;&gt;So before blindly jumping into coding, take a step back&lt;/a&gt;. Understand why the problem or the feature at hand needs to be built in the first place. Next, find the right tools for the job and start coding.&lt;/p&gt;
&lt;p&gt;Some of the authors&apos; recommendations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use plain text for everything. Avoid using binary formats to keep knowledge (such as MS Word).&lt;/li&gt;
&lt;li&gt;Learn some scripting language well to use it for text manipulation (Js, Ruby, Python).&lt;/li&gt;
&lt;li&gt;Learn shell (awk, grep, etc.)&lt;/li&gt;
&lt;li&gt;Have your dotfiles configured and backup them regularly&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Program and refactor deliberately&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In the face of ambiguity, refuse the temptation to guess.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Be wary of premature optimization. It&apos;s always a good idea to make sure an algorithm is a bottleneck before investing your precious time trying to improve it. The less code is there, the fewer chances for bugs to happen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Always be aware of what you are doing&lt;/strong&gt; - if you don&apos;t understand the background of the feature you&apos;re implementing, you may fall victim to false assumptions. Don&apos;t blindly copy/paste code you don&apos;t understand, and don&apos;t do shotgun programming or programming by coincidence, as the book&apos;s authors call it. For example, suppose you don&apos;t know why or how your program works. In that case, you&apos;ll probably end up in a situation where you don&apos;t understand why the code is failing, which would usually result in spending a significant amount of time chasing the piece of code until you (if) know how it was working in the first place.&lt;/p&gt;
&lt;p&gt;A litmus test for the above - can you explain your code to a more junior programmer? If not, perhaps you are relying on coincidences.&lt;/p&gt;
&lt;p&gt;As mentioned in the previous section, don&apos;t jump to coding right away. Instead, &lt;strong&gt;create a plan&lt;/strong&gt;, even if it&apos;s just a to-do list written as comments in your editor or on a napkin. After that, prioritize your efforts by spending time first on the complex parts of the problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don&apos;t be a slave to history,&lt;/strong&gt; meaning that you should not let existing code dictate future code. All code can be replaced if it is no longer appropriate. Even within one program, don&apos;t let what you&apos;ve already done constrain what you do next, be ready to refactor, but keep in mind that this decision may impact the project schedule. The assumption here is that the impact of doing refactor will be less than the cost of not making the change.&lt;/p&gt;
&lt;p&gt;Martin Fowler defines refactoring as a:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The critical parts of this definition are that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The activity is disciplined, not a free-for-all&lt;/li&gt;
&lt;li&gt;External behavior does not change; this is not the time to add features&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To guarantee that the external behavior hasn&apos;t changed, you need good (preferably) automated unit testing that validates the code&apos;s behavior.&lt;/p&gt;
&lt;h2&gt;Use Version Control&lt;/h2&gt;
&lt;p&gt;This advice sounds a bit outdated, but it&apos;s worth a mention. Today&apos;s software development landscape is very different from twenty years ago, at least when version control is considered. &lt;code&gt;git&lt;/code&gt; is deeply entrenched in the development flow, and I, myself, can&apos;t imagine working on a project without version control.&lt;/p&gt;
&lt;p&gt;Additionally, the book suggests using version control for everything we deem important, notes and documentation as an example.&lt;/p&gt;
&lt;p&gt;My take on this is that besides the version control, you should also strive to keep your git history clean and searchable (by using semantic commits, for example) and utilize the VCS for automation whenever possible.&lt;/p&gt;
&lt;h2&gt;Test your code&lt;/h2&gt;
&lt;p&gt;Yet another common-sense advice. And still, one that&apos;s not utilized as much as it should be, in most cases. Testing was important twenty years ago, but today it&apos;s even more critical when we account for a growing number of programs that can &lt;a href=&quot;https://en.wikipedia.org/wiki/Therac-25&quot;&gt;quickly&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Multidata_Systems_International#accident&quot;&gt;kill&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Boeing_737_MAX_groundings&quot;&gt;people&lt;/a&gt; in case of malfunction.&lt;/p&gt;
&lt;p&gt;Book authors even suggest that the test code should be larger than the program source code and that we should treat the test code with the same care as any production code. Keep it decoupled, clean, and robust. Don&apos;t rely on unreliable things like the absolute position of pages in a GUI system, exact timestamps in a server log, or the exact wording of error messages. Testing for these sorts of things will result in fragile (flaky) tests.&lt;/p&gt;
&lt;p&gt;The pragmatic programmer is ruthlessly testing their code. The time it takes to write test code is worth the effort, as it ends up being much cheaper in the long run, with a chance of producing a product with close to zero defects.&lt;/p&gt;
&lt;h2&gt;Automate all the things&lt;/h2&gt;
&lt;p&gt;Automation is the core principle of being a pragmatic programmer. You should find whatever manual task you&apos;ve or someone on your team has been doing and automate them. Automation leaves less space for human error and drastically improves the processes.&lt;/p&gt;
&lt;p&gt;Automation also plays nicely with the other two &quot;legs&quot; of the pragmatic starter kit, version control, and tests. You should automate your processes to run tests on VSC changes and, if stable enough, even deploy the code to production on demand.&lt;/p&gt;
&lt;p&gt;The more stuff you automate, the more time you&apos;ll have to dedicate to the real problems. But (!), don&apos;t fall into &lt;a href=&quot;https://xkcd.com/1205/&quot;&gt;the trap of automating something that&apos;s not really worth the effort&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://imgs.xkcd.com/comics/is_it_worth_the_time.png&quot; alt=&quot;obligatory XKCD&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Pragmatic Teams&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Great things in business are never done by one person. They&apos;re done by a team of people.&quot;
&amp;lt;cite&amp;gt;-- Steve Jobs&amp;lt;/cite&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Teams as a whole should not tolerate broken windows—those slight imperfections that no one fixes. Instead, the team must take responsibility for the quality of the product.&lt;/p&gt;
&lt;p&gt;As a final note, the book says that we, as individuals, should take pride in our work and leave our mark on it. However, we still need to balance this out when working in teams to not become prejudiced in favor of our code and against our coworkers. You shouldn&apos;t jealously defend your code against intruders, and you should treat other people&apos;s code with respect. The Golden Rule (&quot;Do unto others as you would have them do unto you&quot;) and a foundation of mutual respect among the developers are critical to make this work.&lt;/p&gt;
&lt;p&gt;Anonymity can provide a breeding ground for sloppiness, mistakes, sloth, and bad code, especially on large projects. It becomes too easy to see yourself as just a cog in the wheel, producing lame excuses in endless status reports instead of good code.&lt;/p&gt;
&lt;p&gt;While code must be owned, it doesn&apos;t have to be owned by an individual. In fact, Kent Beck&apos;s eXtreme Programming recommends &lt;a href=&quot;http://www.extremeprogramming.org/rules/collective.html&quot;&gt;collective ownership of code&lt;/a&gt; (but this also requires additional practices, such as pair programming, to guard against the dangers of anonymity).&lt;/p&gt;
&lt;p&gt;In the end, what you want of your career as a pragmatic programmer is for other people to recognize your signature. They see the feature or program built by you and expect it to be solid, well-written, tested, and documented.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I suggest this book to everyone; it&apos;s an easy and interesting read, even though senior developers are less likely to learn something new from it. Also, this is just a quick and short(&lt;em&gt;ish&lt;/em&gt;) overview of the book&apos;s content, I haven&apos;t covered everything I&apos;ve learned from it, and there is a lot of stuff that&apos;s also worth reading about, such as Orthogonality, Design by Contract, debugging, and Tracer Bullets technique, and more.&lt;/p&gt;
</content:encoded><category>Software Engineering</category></item><item><title>Shellscripting</title><link>https://darkotasevski.dev/writing/shellscripting/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/shellscripting/</guid><description>Introduction to Shell programming</description><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;Variables and Shebang&lt;/strong&gt;&lt;/h2&gt;
&lt;hr /&gt;
&lt;h3&gt;Small intro to shell programming&lt;/h3&gt;
&lt;p&gt;The Shell is the standard interface to every Unix and Linux system; users and administrators alike have experience with the shell, and combining commands into scripts is a natural progression. However, this is just a tip of the iceberg.&lt;/p&gt;
&lt;p&gt;I&apos;ve spent some time lately learning about shell and writing scripts, and I&apos;ve realized that the shell is actually a full programming language with variables and functions, and also more advanced structures such as arrays (including associative arrays) and being so directly linked to the kernel it has native IO primitives built into it&apos;s very syntax, as well as process and job control.&lt;/p&gt;
&lt;p&gt;I&apos;ve planned this as a series of a few posts, and I&apos;ll try to be concise and on the point.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;So, what is shellscripting?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Shellscripting is writing a series of commands for the shell to execute. It can combine lengthy and repetitive sequences of commands into a single and simple script, which can be stored and executed anytime, which is great for automating tasks. This reduces the effort required by the end user.&lt;/p&gt;
&lt;p&gt;Script&apos;s commands are being executed by the interpreter (shell), one by one, and everything you can type in the command line you can also put in the script.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Before running scripts, we need to set up permissions for execution with: &lt;code&gt;chmod 755 script.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We can then run the script with &lt;code&gt;./script.sh&lt;/code&gt; via command line.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;#!&lt;/code&gt; (shebang) specifies binary of the shell (interpreter) we want to execute the script, for example:
&lt;code&gt;#! /bin/bash&lt;/code&gt;, &lt;code&gt;#! /bin/zsh&lt;/code&gt; or for the best portability &lt;code&gt;#! /bin/sh&lt;/code&gt; (this will run system shell).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note that the most of code in this series is tested only with &lt;code&gt;bash&lt;/code&gt; and &lt;code&gt;zsh&lt;/code&gt; shell, most sh scripts can be run by Bash without modification, but some stuff wont work.&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#! /bin/sh
sleep 90
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we execute the script that contains &lt;code&gt;#!&lt;/code&gt; what actually happens is that interpreter is executed and the path used to call the script is passed as an argument. To confirm that, let&apos;s say we have &lt;code&gt;sleepy.sh&lt;/code&gt; script, then we can run the script with &lt;code&gt;./sleepy.sh &amp;amp;&lt;/code&gt;, where &lt;code&gt;&amp;amp;&lt;/code&gt; is used (it seems) to return the PID of the script execution process, and then we can run &lt;code&gt;ps -fp [PID]&lt;/code&gt; to see process info:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UID PID PPID C STIME TTY TIME CMD
505 65418 59985 0 7:09PM ttys000 0:00.01 /bin/zsh ./sleepy.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see here that &lt;code&gt;./sleepy.sh&lt;/code&gt; is passed to my &lt;code&gt;/bin/zsh&lt;/code&gt; binary as an argument.&lt;/p&gt;
&lt;p&gt;If a script doesn&apos;t contain &lt;code&gt;#!&lt;/code&gt; commands are executed with default shell, but it&apos;s the best practice to be explicit as different shells have slightly varying syntax.&lt;/p&gt;
&lt;p&gt;Also, we don&apos;t have to use only shells as interpreters for scripts. We can also use other binaries like &lt;code&gt;python&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#! /usr/bin/python

print &quot;This is a Python script&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;chmod 755 hi.py
./hi.py
This is a Python script
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;Variables&lt;/h3&gt;
&lt;p&gt;Variables are storage locations that have a name, and you can think of them as name-value pairs.
Syntax used to create a variable is: &lt;code&gt;VARIABLE_NAME=&quot;Value&quot;&lt;/code&gt;. It&apos;s important to note that variable names are case sensitive, and that, by convention, variable names should be all in uppercase. Also make sure to not use spaces after and before &lt;code&gt;=&lt;/code&gt; sign, when declaring a variable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;By default all variables are global, also they have to be defined before used.&lt;/strong&gt;
Variables can be defined in the functions (we&apos;ll talk about them eventually), but we cannot access them before a function is called.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function var(){
    FUNC_VAR=1
}
# FUNC_VAR is not defined at this point and this will not return anything
echo $FUNC_VAR
var # This is how we call a function in the shell
# FUNC_VAR is now available because the function has been called
echo $FUNC_VAR # Output: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Valid variable names can consist of letters, numbers, and underscores, except that number cannot be the first char in the name.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Valid names
DARK_JEDI=&quot;Vader&quot;
GR4Y_J3DI=&quot;Ahsoka&quot;
Regular_Jedi=&quot;Obi-Wan&quot;

# Invalid names
3DARK_LORDS=&quot;Vader Sidius Plagueis&quot;
TWO-REBELS=&quot;Solo Leia&quot;
ONE@SHIP=&quot;Ebon Hawk&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;#! /bin/bash
MY_SHELL=&quot;zsh&quot;
echo &quot;I like the $MY_SHELL shell&quot; # Output: I like the zsh shell
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can also enclose the variable name in curly braces:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MY_SHELL=&quot;zsh&quot;
echo &quot;I like the ${MY_SHELL} shell&quot; # Output: I like the zsh shell
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Curly braces syntax is optional unless you need to precede or follow up the variable with additional data, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MY_SHELL=&quot;bash&quot;
echo &quot;I&apos;m ${MY_SHELL}ing on my keyboard!&quot; # Output: I&apos;m bashing on my keyboard.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without curly braces this wouldn&apos;t work as the interpreter will take that &lt;code&gt;ing&lt;/code&gt; following the name variable as a part of the variable name.&lt;/p&gt;
&lt;p&gt;Another best practice is to enclose variables in quotes, when working with them, to prevent some unexpected side effects.&lt;/p&gt;
&lt;p&gt;We can also assign the output of the command to a variable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SERVER_NAME=$(hostname)
echo &quot;You are running this script on ${SERVER_NAME}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Local variables&lt;/h4&gt;
&lt;p&gt;Local vars are created with &lt;code&gt;local&lt;/code&gt; keyword, and &lt;strong&gt;only functions can have the local variables&lt;/strong&gt;, so they can only be accessed within the function where they&apos;re declared.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function myFunc(){
    local LOCAL_VAR=&quot; I&apos;m locally scoped&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s the best practice to use only local variables inside functions.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;That&apos;s it for now, I&apos;ll write a bit about tests and loops in the next one. Thanks for reading, and if you have any question, just ask away!&lt;/p&gt;
</content:encoded><category>Software Engineering</category></item><item><title>Shellscripting: Conditional Execution</title><link>https://darkotasevski.dev/writing/shellscripting-conditionals/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/shellscripting-conditionals/</guid><description>Conditionals in shell programming</description><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/mzfpomwt6xfadtk9lpei.png&quot; alt=&quot;Alt text of image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is the second part of the series on Shellscripting. In case you&apos;ve missed it, you can find the first part &lt;a href=&quot;https://dev.to/puritanic/shellscripting-27bl&quot;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Also, note that the most of the code is tested only with the &lt;code&gt;bash&lt;/code&gt; and &lt;code&gt;zsh&lt;/code&gt; shells, it may not work with other shells.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conditional execution&lt;/strong&gt; means that you can choose to execute code only if certain conditions are met. Without this capability, all you would be able to do is execute one command after another after another. The ability to test a variety of things about the state of the system, and of the environment variables of the process, means that a shell script can do far more powerful things that would otherwise be possible. In this post, we are going to explore &lt;code&gt;test operators&lt;/code&gt;, &lt;code&gt;if/then/else &lt;/code&gt;conditionals, and &lt;code&gt;case&lt;/code&gt; statements.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;test&lt;/code&gt; aka &lt;code&gt;[&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;With tests we can check for example: if the file exists, if a number is greater than another, compare if strings are equal...&lt;/p&gt;
&lt;p&gt;Syntax:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ condition-to-test-for ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ -e /etc/passwd ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As heading above says, another name for &lt;code&gt;test&lt;/code&gt; is &lt;code&gt;[&lt;/code&gt;. It is also a shell builtin (which means that the shell itself will interpret &lt;code&gt;[&lt;/code&gt; as &lt;code&gt;test&lt;/code&gt;, even if your Unix environment is set up differently). When &lt;code&gt;[&lt;/code&gt; is called, it requires a &lt;code&gt;]&lt;/code&gt; around its arguments, but otherwise, it does the same work.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;test  -e /etc/passwd
# as above so below
[ -e /etc/passwd ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tests if &lt;code&gt;etc/passwd&lt;/code&gt; exists, and if it does this returns &lt;code&gt;true&lt;/code&gt; - command exit status of &lt;code&gt;0&lt;/code&gt;. If it doesn&apos;t exist the command exits with the exit status of &lt;code&gt;1&lt;/code&gt; (more on exit statuses in next post).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gotcha&lt;/strong&gt;: The spaces around the &lt;code&gt;[&lt;/code&gt; and &lt;code&gt;]&lt;/code&gt; symbols are required! For example:
&lt;code&gt;[-e /etc/passwd ]&lt;/code&gt; will not work; it is interpreted as &lt;code&gt;test-e /etc/passwd ]&lt;/code&gt; which errors because &lt;code&gt;]&lt;/code&gt; doesn&apos;t have a beginning &lt;code&gt;[&lt;/code&gt;. &lt;code&gt;[&lt;/code&gt; is actually a program, and just like &lt;code&gt;ls&lt;/code&gt; and other programs, it must be surrounded by spaces.
&lt;strong&gt;Moral of the story: Put spaces around all your operators.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: You can reverse the results of the test with &lt;code&gt;!&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if [ ! -r “$1” ]; then echo &quot;File $1 is not readable – skipping.&quot;; fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see &lt;code&gt;test&lt;/code&gt; is a simple but powerful comparison utility. For full details, run &lt;code&gt;man test&lt;/code&gt; on your system, but here are some usages and typical examples:&lt;/p&gt;
&lt;p&gt;File test operators:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-d FILE #True if the file is a directory
-e FILE #True if the file exists
-f FILE #True if the file exists and it&apos;s regular file
-r FILE #True if the file is readable by you
-s FILE #True if the file exists and it&apos;s not empty
-w FILE #True if the file is writable by you
-x FILE #True if the file is executable by you
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;String test operators:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-z STRING #True if the string is empty
-n STRING #True if the string is not empty
STRING1 = STRING2 #True if the strings are equal
STRING1 != STRING2 #True if the strings are not equal
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Arithmetic tests:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;arg1 -eq arg2 #True if the arguments are equal
arg1 -ne arg2 #True if the arguments are not equal
arg1 -lt arg2 #True if the arg1 is less than arg2
arg1 -le arg2 #True if arg1 is less than or equal to arg2
arg1 -gt arg2 #True if arg1 is greater than arg2
arg1 -ge arg2 #True if arg1 is greater than or equal to arg2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;It is possible to combine tests, and/or chain multiple commands by using the &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt; operators. These perform a Logical AND and Logical OR, respectively.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&amp;amp; = AND&lt;/code&gt;
&lt;code&gt;mkdir /tmp/bak &amp;amp;&amp;amp; cp test.txt /tmp/bak&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The command that follows &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; will be executed if and only the previous command succeeds (aka exits with &lt;code&gt;0&lt;/code&gt; exit status).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;|| = OR&lt;/code&gt;
&lt;code&gt;cp test.txt /tmp/bak || cp test.txt /tmp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;||&lt;/code&gt; operator performs a Logical OR, so when it only matters that one of the conditions is met, but not which one, this is the feature to use.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#! /bin/bash
HOST=&quot;google.com&quot;
ping -c 1 $HOST &amp;amp;&amp;amp; echo &quot;$HOST reachable.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;IF/THEN&lt;/h3&gt;
&lt;p&gt;Almost every programming language has an if/then/else construct, and the shell is no exception. The syntax uses square brackets to perform a test, and the &lt;code&gt;then&lt;/code&gt; and &lt;code&gt;fi&lt;/code&gt; statements are required, acting just like the { and } curly brackets in C and some other languages.&lt;/p&gt;
&lt;p&gt;Syntax:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if [ condition ]
then
    statements for when the condition is true
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Other than the line break after the &lt;code&gt;then&lt;/code&gt;, all these line breaks are required or can be replaced with semicolons. To remind: the spaces around the &lt;code&gt;[&lt;/code&gt; and &lt;code&gt;]&lt;/code&gt; symbols are also required, so this can be reduced (pls don&apos;t) at best to:
&lt;code&gt;if [ condition ];then statements;fi&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is quite common to use the semicolon to put the then on the same line as the if.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MY_SHELL=&quot;zsh&quot;

if [ &quot;$MY_SHELL&quot; = &quot;zsh&quot; ]
    then
        echo &quot;You are the zsh shell user!&quot;
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ELSE&lt;/h4&gt;
&lt;p&gt;It may be that you want to run the command if possible, but if it can’t be done, then continue execution of the script. One (simpler and the most common) way to do this would be to use ELSE statement:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if [ condition ]; then
    statements for when the condition is true
else
    statements for when the condition is false
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

# Check for likely causes of failure
if [ -r &quot;$1&quot; ]; then
    cat &quot;$1&quot;
else
    echo &quot;Error: $1 is not a readable file.&quot;
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This snippet tries to &lt;code&gt;cat&lt;/code&gt; the file passed to it as its first parameter (&lt;code&gt;&quot;$1&quot;&lt;/code&gt; putting double quotes around it to allow for filenames including spaces) and spits out an error message if it failed to do so.&lt;/p&gt;
&lt;h4&gt;ELIF&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;elif&lt;/code&gt; is a construct that allows you to add conditions to the &lt;code&gt;else&lt;/code&gt; part of an &lt;code&gt;if&lt;/code&gt; statement. It is short for &quot;else if&quot; so that a long string of possible actions can be written more concisely. This makes it easier to write, easier to read, and most importantly, easier to debug.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash 
OS=`uname -s`
if [ &quot;$OS&quot; = &quot;FreeBSD&quot; ]; then
    echo &quot;This Is FreeBSD&quot;
elif [ &quot;$OS&quot; = &quot;CYGWIN_NT-5.1&quot; ]; then
    echo &quot;This is Cygwin&quot;
elif [ &quot;$OS&quot; = &quot;SunOS&quot; ]; then
    echo &quot;This is Solaris&quot;
elif [ &quot;$OS&quot; = &quot;Darwin&quot; ]; then
    echo &quot;This is Mac OSX&quot;
elif [ &quot;$OS&quot; = &quot;Linux&quot; ]; then
    echo &quot;This is Linux&quot;
else
    echo &quot;Failed to identify this OS&quot;
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is &lt;em&gt;much&lt;/em&gt;, &lt;em&gt;much&lt;/em&gt; more readable than the nested &lt;code&gt;else&lt;/code&gt; code hell this could turn into.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;code&gt;case&lt;/code&gt; statement&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;case&lt;/code&gt; provides a much cleaner, easier-to-write, and far more readable alternative to the &lt;code&gt;if/then/else&lt;/code&gt; construct, particularly when there are a lot of possible values to test for. With case, you list the values you want to identify and act upon, and then provide a block of code for each one.
One common place for case statements use are system startup scripts.
Syntax:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;case &quot;$VAR&quot; in
    pattern_1)
        # Some commands here.
        ;; # Execution will stop when the double semicolon is reached 
    pattern_n)
        # Some commands here.
        ;;
esac
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
OS=`uname -s`

case &quot;$OS&quot; in
    FreeBSD) echo &quot;This is FreeBSD&quot; ;;
    CYGWIN_NT-5.1) echo &quot;This is Cygwin&quot; ;;
    SunOS) echo &quot;This is Solaris&quot; ;;
    Darwin) echo &quot;This is Mac OSX&quot; ;;
    Linux) echo &quot;This is Linux&quot; ;;
    *) echo &quot;Failed to identify this OS&quot; ;;
esac
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although it looks like a special directive, the &lt;code&gt;*&lt;/code&gt; is simply the most generic wildcard possible, as it will match absolutely any string. This also suggests that we are able to do more advanced pattern matching like &lt;code&gt;RegEx&lt;/code&gt;, for example.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that the patterns are case sensitive.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A less well-known feature of the bash implementation of &lt;code&gt;case&lt;/code&gt; is that you can end the statement with &lt;code&gt;;;&amp;amp;&lt;/code&gt; or &lt;code&gt;;&amp;amp;&lt;/code&gt; instead of only &lt;code&gt;;;&lt;/code&gt;. While &lt;code&gt;;;&lt;/code&gt; means that none of the other statements will be executed, if you end a statement with &lt;code&gt;;;&amp;amp;&lt;/code&gt; all subsequent cases will still be evaluated. If you end a statement with &lt;code&gt;;&amp;amp;&lt;/code&gt;, the case will be treated as having matched.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

read -p &quot;Give me a word: &quot; input
echo -en &quot;That&apos;s &quot;
case $input in
  *[[:digit:]]*) echo -en &quot;numerical &quot; ;;&amp;amp;
  *[[:lower:]]*) echo -en &quot;lowercase &quot; ;;&amp;amp;
  *[[:upper:]]*) echo -en &quot;uppercase &quot; ;;&amp;amp;
  *) echo &quot;input.&quot; ;;
esac

$ ./case1.sh
Give me a word: Hello 123
That&apos;s numerical lowercase uppercase input.
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;This feature is specific to the bash shell; it is not a standard feature of the Bourne shell, so if you need to write a portable script, do not expect this to work. It will cause a syntax error message on other shells.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;This post has covered the various ways of controlling conditional execution — from the simple &lt;code&gt;if/then/else construct&lt;/code&gt;, through the different things that can be done with &lt;code&gt;test&lt;/code&gt;, through to the more flexible &lt;code&gt;case&lt;/code&gt; statement for matching against different sets of input.&lt;/p&gt;
&lt;p&gt;I must admit that writing about shell programming seemed like two or three posts tops in the beginning, but there is a &lt;em&gt;lot&lt;/em&gt; to be covered, and even this and previous posts are not even the half of everything that can be learned about topics I&apos;ve written about. I really recommend &lt;a href=&quot;http://tldp.org/LDP/abs/html/index.html&quot;&gt;Advanced Bash-Scripting Guide&lt;/a&gt; and &lt;a href=&quot;http://shop.oreilly.com/product/9780596005955.do&quot;&gt;Classic Shell Scripting&lt;/a&gt; if you want to learn shell programming in more depth. I&apos;ll write a bit about Positional parameters, exit codes and (hopefully) functions in the next one. Thanks for reading!&lt;/p&gt;
</content:encoded><category>Software Engineering</category></item><item><title>Shellscripting: Functions</title><link>https://darkotasevski.dev/writing/shellscripting-functions/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/shellscripting-functions/</guid><description>Functions in shell programming</description><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is part three of my post series about Shellscripting, you can check out previous posts here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/puritanic/shellscripting-27bl&quot;&gt;Shellscripting: Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/puritanic/shellscripting-conditional-execution-3kgm&quot;&gt;Shellscripting: Conditional Execution&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;For functions we can say that they&apos;re shellscripts within shellscript.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the main reasons why we are using functions is to follow the DRY principle, which means that we should write a function once, and then we can use it many times. This can sometimes drastically reduce the script length, and also it&apos;s much easier to maintain as we have a single function to edit and troubleshoot.&lt;/p&gt;
&lt;p&gt;Function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hides implementation details from the main script, simplifying the shell script’s main body of code&lt;/li&gt;
&lt;li&gt;Can be replaced if the underlying detail it works with is changed&lt;/li&gt;
&lt;li&gt;Can be tested over and over again as a small piece of a larger script, with changing input values to prove that the code is correct&lt;/li&gt;
&lt;li&gt;Allows consistent reuse of code from within the script, or even between several shell scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/4l1bsjjtpua17f1w4v1o.png&quot; alt=&quot;xkcd&quot; /&gt;
&amp;lt;figcaption&amp;gt; &amp;lt;a href=&quot;https://xkcd.com/1168/&quot;&amp;gt;Slightly relevant xkcd 😄&amp;lt;/a&amp;gt;&amp;lt;/figcaption&amp;gt;&lt;/p&gt;
&lt;h2&gt;Exit statuses&lt;/h2&gt;
&lt;p&gt;Before we dive into functions it&apos;s essential to know that every command executed in shell returns an exit status in the range of 0 to 255.
De facto status for success is &lt;code&gt;0&lt;/code&gt;, all others are codes for an error condition.
This codes can be used in scripts for throwing and checking errors. Usually, we can find what various exit statuses mean by checking the documentation for that error code or look into the source code.&lt;/p&gt;
&lt;p&gt;We can use &lt;code&gt;$?&lt;/code&gt; to check the exit status of the previously executed command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls dir/
echo $? 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;dir&lt;/code&gt; exists &lt;code&gt;echo $?&lt;/code&gt; will return &lt;code&gt;0&lt;/code&gt; status code, otherwise, it should return &lt;code&gt;2&lt;/code&gt;, the error code for directory not found.&lt;/p&gt;
&lt;p&gt;We can explicitly define the return codes with &lt;code&gt;exit&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#! bin/bash
HOST=&quot;google.com&quot;

ping -c 1 $HOST

if [&quot;$?&quot; -ne &quot;0&quot;]
then
    echo &quot;$HOST unreachable&quot;
    exit 1
fi
exit 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simply use the &lt;code&gt;exit&lt;/code&gt; command in your script and follow it with the integer in the range of 0 to 255. If we do not specify the return code with the &lt;code&gt;exit&lt;/code&gt; command, then the exit status of the previously executed command in the shellscript will be used as the exit status. This is also true if we do not include &lt;code&gt;exit&lt;/code&gt; command at all.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Whenever the &lt;code&gt;exit&lt;/code&gt; command is reached the shellscript will stop running.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;All functions have an exit status. We can explicitly return status within the function with &lt;code&gt;return&lt;/code&gt; keyword:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function myFunc() {
    return 1 # returning exit status code 1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, status can be returned implicitly with the exit status of the last command executed in the function.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Positional parameters&lt;/h3&gt;
&lt;p&gt;Positional parameters are the variables which we can use to specify arguments passed to the function via command line. For example, if we execute the script like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;script.sh param1 param2 param3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Inside that script we can access all command line arguments like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$0: &quot;script.sh&quot; # $0 is always the name of the script
$1: &quot;param1&quot; # $1 is the first parameter,
$2: &quot;param2&quot; # $2 is the second
$3: &quot;param3&quot; # $3 is the third, and so on...
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: You cannot change the values of these variables&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A practical example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# args.sh
#!/bin/bash
echo &quot;I was called as $0&quot;
echo &quot;My first argument is: $1&quot;
echo &quot;My second argument is: $2&quot;
echo &quot;I was called with $# parameters.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ ./args.sh one two
I was called as ./args.sh
My first argument is: one
My second argument is: two
I was called with 2 parameters.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can use &lt;code&gt;$#&lt;/code&gt; to check with how much parameters script was called, which is a good way to check if the user has executed the script with enough number of args, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat argCheck.sh
#!/bin/bash
if [ &quot;$#&quot; -eq &quot;2&quot; ]; then
    echo &quot;The script was called with exactly two parameters. Let’s continue.&quot;
else
    echo &quot;You provided $# parameters, but 2 are required.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can use &lt;code&gt;$@&lt;/code&gt; variable when we want to loop through script parameters:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# This will loop through all parameter passed to the script when executed
for USER in &quot;$@&quot;; do
    passwd -l $USER # lock the account
done
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h4&gt;Creating a function&lt;/h4&gt;
&lt;p&gt;Let&apos;s get back to the functions.
It&apos;s important to note that function must be defined before it&apos;s called, it is conventional to define functions at the start of the file, although this is not strictly necessary.
The block of code defined as a function can be declared in one of three different ways, depending on the exact shell in use. The standard Bourne shell syntax uses the function name followed immediately by a pair of parentheses &lt;code&gt;()&lt;/code&gt; and curly brackets &lt;code&gt;{}&lt;/code&gt; around the code itself:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# The most common way
myFunc() {
    # Code
    echo &quot;Hello&quot;
}
# function keyword is optional
# so this is also correct
function mySecondFunc () {
    # More code
    echo &quot;World&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is a third syntax, which is not accepted by the Bourne shell, although bash and ksh both accept it. Instead of following the function name by a pair of parentheses, the function name is preceded by the keyword function:
&lt;code&gt;function myFunc&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;As far as I&apos;ve learned so far, the first one (without keyword &lt;code&gt;function&lt;/code&gt;) is the most commonly used as it&apos;s accepted by all shells. The second syntax is also used frequently and by using the &lt;code&gt;function&lt;/code&gt; keyword, it provides a more clear declaration that it is a function. Regarding the 3rd one, I couldn&apos;t find any information about it, except that it exists :|&lt;/p&gt;
&lt;h4&gt;Calling a function&lt;/h4&gt;
&lt;p&gt;We can call and execute the function by simply typing its name in the script (after it&apos;s been defined first):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function hello() {
    echo &quot;Hello World!&quot;
}

hello
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;function hello() {
    echo &quot;Hello ${1}!&quot;
}

hello World # Output: Hello World
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Functions can also call other functions. Here we can see there that the &lt;code&gt;hello&lt;/code&gt; function calls &lt;code&gt;now&lt;/code&gt; function before it&apos;s declared, but that&apos;s okay as the &lt;code&gt;now&lt;/code&gt; function gets read into the script before the &lt;code&gt;hello&lt;/code&gt; function is called, so in the order of the execution it&apos;s defined before it&apos;s used.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function hello() {
    now
}
function now() {
    echo &quot;$(date +%r)&quot;
}

hello # Output: 02:15:36 PM
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But, for example, something like this won&apos;t work:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function hello() {
    now
}
hello # hello is called before now is defined
function now() {
    echo &quot;$(date +%r)&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Besides calling other functions, shell functions can also call themselves recursively. A simple example to demonstrate this is the mathematical factorial function.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat factorial.sh 
#!/bin/bash

function factorial() {
    if [ &quot;$1&quot; -gt &quot;1&quot; ]; then 
        previous=`expr $1 - 1` 
        parent=`factorial $previous` 
        result=`expr $1 \* $parent` 
        echo $result
    else
        echo 1
    fi
}
factorial $1
$ ./factorial.sh 6 720
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should be very careful when working with recursive functions tho, especially if you are creating files in them, you could end up with more open files than allowed by the system.&lt;/p&gt;
&lt;p&gt;Functions also have &lt;code&gt;positional parameters&lt;/code&gt;, and &lt;code&gt;$@&lt;/code&gt; can also be used to retrieve the list of all passed arguments.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# $0 is the script itself, not the function name
function helloFunc(){
    echo &quot;Hello ${1} ${2}!&quot;
}
helloFunc Shell World
# Output: Hello Shell World!
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Functions also have access to all global variables. But, as a reminder, it&apos;s the best practice to use only &lt;code&gt;local&lt;/code&gt; variables inside the functions to avoid side effects, which can eventually cause bugs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;There is much more to be said about the functions in shellscript, so consider this post just as a small introduction to their usage in scripting. In the next post, I&apos;ll write a bit about Wildcards, Character Classes and about logging and debugging shellscript.
Thanks for reading!&lt;/p&gt;
</content:encoded><category>Software Engineering</category></item><item><title>Thinking Clearly with LLMs: Mental Models and Cognitive Pitfalls in Prompt Engineering</title><link>https://darkotasevski.dev/writing/thinking-clearly-llm/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/thinking-clearly-llm/</guid><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Large Language Models feel intelligent in conversation, but their behavior is fundamentally alien to human cognition. They don&apos;t think like we do, reason like we do, or understand context like we do. Yet we keep trying to interact with them as if they were human collaborators.&lt;/p&gt;
&lt;p&gt;This mismatch leads to fragile, confusing, and often overconfident prompt engineering. We write prompts that work sometimes, fail mysteriously, and leave us scratching our heads about what went wrong.&lt;/p&gt;
&lt;p&gt;The solution isn&apos;t better prompt templates or more sophisticated techniques. It&apos;s &lt;strong&gt;thinking more clearly&lt;/strong&gt; about what LLMs actually are and how they actually work.&lt;/p&gt;
&lt;p&gt;This post will walk through mental models that accurately describe how LLMs behave in practice, cognitive pitfalls that distort our reasoning when writing prompts, and practical frameworks for building more reliable AI systems.&lt;/p&gt;
&lt;p&gt;Whether you&apos;re building AI features into your products or just trying to understand these systems better, this guide will help you avoid the traps that lead to brittle, confusing, or overconfident prompt engineering.&lt;/p&gt;
&lt;h2&gt;LLMs Don&apos;t Think Like We Do&lt;/h2&gt;
&lt;p&gt;The fundamental problem is that we anthropomorphize LLMs. We treat them like they have intentions, understanding, or agency. But they don&apos;t.&lt;/p&gt;
&lt;p&gt;An LLM is a statistical pattern matcher trained on vast amounts of text. It predicts the next token based on what came before, nothing more. Yet we keep trying to reason with it as if it were a human collaborator. This fundamental misunderstanding is what &lt;a href=&quot;https://dl.acm.org/doi/10.1145/3442188.3445922&quot;&gt;Emily Bender and colleagues&lt;/a&gt; called the &quot;stochastic parrot&quot; problem in their influential paper.&lt;/p&gt;
&lt;p&gt;This mismatch creates fragile prompts that work in some contexts but fail in others, confusing interactions where the model seems to understand but then behaves unexpectedly, overconfidence in the model&apos;s capabilities, and inefficient development cycles spent debugging &quot;unpredictable&quot; behavior.&lt;/p&gt;
&lt;p&gt;The solution is to develop accurate mental models of how LLMs actually work and to recognize the cognitive pitfalls that lead us astray.&lt;/p&gt;
&lt;h2&gt;Durable Mental Models for Working with LLMs&lt;/h2&gt;
&lt;h3&gt;The Stochastic Parrot&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Core insight&lt;/strong&gt;: LLMs are sophisticated pattern matchers, not reasoning engines.&lt;/p&gt;
&lt;p&gt;At their heart, LLMs predict the next token based on statistical patterns in their training data. They don&apos;t &lt;a href=&quot;https://arxiv.org/abs/1801.00631&quot;&gt;&quot;understand&quot;&lt;/a&gt; concepts in the way humans do, but they recognize patterns and continue them.&lt;/p&gt;
&lt;p&gt;This means ambiguity is problematic because vague prompts can produce unpredictable results. Precision matters, with specific, well-structured prompts working better. Hallucination is inevitable, as models will confidently generate plausible but false information. And context is everything, as the model&apos;s behavior depends heavily on the immediate context.&lt;/p&gt;
&lt;p&gt;The practical takeaway? Write prompts that are explicit, specific, and leave little room for interpretation. Don&apos;t assume the model will &quot;figure out&quot; what you mean.&lt;/p&gt;
&lt;h3&gt;The Simulator&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Core insight&lt;/strong&gt;: LLMs can simulate roles, workflows, or behaviors based on the context you provide.&lt;/p&gt;
&lt;p&gt;When you write a prompt like &quot;Act as a helpful assistant,&quot; the model doesn&apos;t become an assistant. It samples from patterns in its training data that match how helpful assistants tend to speak and behave. In effect, you&apos;re constructing a lightweight simulation that persists for the duration of the prompt.&lt;/p&gt;
&lt;p&gt;This is powerful. Specifying a role like developer, analyst, or teacher can shift the model&apos;s tone, structure, and output format. Roles help set expectations and improve consistency, especially across multiple turns. However, if you mix conflicting roles or instructions, the simulation can break down or become incoherent.&lt;/p&gt;
&lt;p&gt;The practical takeaway? Be intentional about the role you want the model to play. Specify the persona, goals, and constraints clearly.&lt;/p&gt;
&lt;h3&gt;The Conversational Mirror&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Core insight&lt;/strong&gt;: LLMs echo your tone, ambiguity, and structure.&lt;/p&gt;
&lt;p&gt;The model&apos;s output quality and style directly reflects the quality and style of your input. If your prompt is vague, the response will be vague. If your prompt is precise, the response will be more precise.&lt;/p&gt;
&lt;p&gt;This means input quality maps directly to output quality, following the classic &quot;garbage in, garbage out&quot; principle. The model adopts your communication style through tone matching. Structure matters, with well-structured prompts producing well-structured responses. And each interaction builds on the previous ones for iterative improvement.&lt;/p&gt;
&lt;p&gt;The practical takeaway? Write prompts as if you&apos;re writing for a very smart but literal colleague. Be clear, specific, and well-structured.&lt;/p&gt;
&lt;h3&gt;The Prompt is the Program&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Core insight&lt;/strong&gt;: Prompting is declarative, dynamic programming with text as the universal interface.&lt;/p&gt;
&lt;p&gt;Your prompt is the &quot;code&quot; that runs on the LLM. It defines the inputs, outputs, constraints, and behavior. Like any program, it needs to be well-structured, modular, and maintainable. This aligns with the broader shift toward what Andrej Karpathy called &lt;a href=&quot;https://karpathy.medium.com/software-2-0-a64152b37c35&quot;&gt;Software 2.0&lt;/a&gt;, where programs are defined not just by explicit logic, but by data, models, and context.&lt;/p&gt;
&lt;p&gt;Complex prompts often create layered, role-specific micro-worlds, with each layer having its own role, goals, and constraints. The model navigates these layers to produce appropriate responses. This layering can create sophisticated behavior, but role conflicts can arise when different layers have conflicting goals.&lt;/p&gt;
&lt;p&gt;Since most interaction with an LLM happens through text, the prompt acts like its user interface. And just like any UI, clarity, structure, and consistency go a long way. A clear, well-structured prompt makes the model easier to work with and more likely to respond reliably. When your prompts follow consistent patterns, you get more predictable outputs. Over time, you&apos;ll spot fedback loops, what works, what breaks, where ambiguity creeps in, and that helps you refine things. This is why modularity matters. Break complex prompts into smaller, reusable parts. Keep track of what you change and why, just like you would with code.&lt;/p&gt;
&lt;p&gt;The practical takeaway? Treat prompt engineering like software engineering. Use version control, test the model, document your approach, and design your prompts with the same care you&apos;d use for a user interface.&lt;/p&gt;
&lt;h2&gt;Cognitive Pitfalls in Prompt Engineering&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I&apos;ve found knowing about the &lt;strong&gt;Einstellung Effect&lt;/strong&gt; and &lt;strong&gt;Type III Error&lt;/strong&gt;, in particular, instrumental in LLM prompting. Knowing about these two biases can help you a lot with structuring your prompts correctly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Type III error: Solving the Wrong Problem&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: Framing mistakes lead to elegant failures.&lt;/p&gt;
&lt;p&gt;In statistical reasoning, most people are familiar with Type I errors (false positives) and Type II &lt;a href=&quot;https://en.wikipedia.org/wiki/Type_I_and_type_II_errors&quot;&gt;errors&lt;/a&gt; (false negatives). Less well known, but just as important in prompt engineering, is the &lt;a href=&quot;https://en.wikipedia.org/wiki/Type_III_error&quot;&gt;&lt;strong&gt;Type III error&lt;/strong&gt;&lt;/a&gt;: solving the wrong problem.&lt;/p&gt;
&lt;p&gt;This happens when a prompt is well-crafted and followed precisely by the model, but the output is irrelevant or unhelpful because the underlying task was misunderstood. The model does exactly what it was asked to do, but the prompt was aimed at the wrong goal. The failure is not in the execution, but in the framing.&lt;/p&gt;
&lt;p&gt;You might assume the model can reason when it is really just predicting. You might misread what the user needs, focus on the wrong aspect of the workflow, or design an elegant prompt that misses the point entirely. Sometimes, the entire interaction is shaped more by the capabilities of the LLM than by what the broader system or product actually requires.&lt;/p&gt;
&lt;p&gt;To avoid this pitfall, start with the actual problem rather than jumping to an solution. Clarify the task intent before writing any prompts. Test your assumptions about the problem at hand and what the model can actually do. Before writing a single token, step back and define the problem clearly. Only then should you start designing the prompt.&lt;/p&gt;
&lt;p&gt;Start with the problem. Then design the prompt. Not the other way around.&lt;/p&gt;
&lt;h3&gt;The Einstellung Effect: Prompt Fixation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: Reuse bias leads to suboptimal prompts.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Einstellung_effect&quot;&gt;Einstellung effect&lt;/a&gt; is a cognitive bias where a familiar solution blocks recognition of a better one. In the context of prompt engineering, this often appears when a previously successful prompt becomes a default, even when it no longer fits the task.&lt;/p&gt;
&lt;p&gt;Common mistakes include reusing the same prompt structure for different problems, not experimenting with different approaches, getting stuck in familiar patterns, or ignoring evidence that suggests a different approach.&lt;/p&gt;
&lt;p&gt;To avoid this pitfall, stay flexible and iterative. Experiment with different prompt structures. Question your assumptions regularly. And look for evidence that suggests different approaches might work better.&lt;/p&gt;
&lt;h3&gt;Prompt Overfitting&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: A prompt that works on one example might fail everywhere else.&lt;/p&gt;
&lt;p&gt;Prompt overfitting happens when you design and test a prompt using only one or two inputs, then assume it will behave the same way in general. It feels like success, but it&apos;s often an illusion. The prompt may have worked because the input was easy, familiar, or unintentionally aligned with the model&apos;s defaults. Once the input shifts, longer content, edge cases, different tone, the output breaks down or drifts unpredictably.&lt;/p&gt;
&lt;p&gt;This is especially risky when you&apos;re working with limited test data or optimizing prompts by trial and error on a small set of examples. You end up with something that looks robust, but actually performs well only in narrow conditions.&lt;/p&gt;
&lt;p&gt;To avoid this, evaluate prompts across a wide range of realistic inputs. Include examples that vary in structure, tone, length, or ambiguity. Treat your prompt like a function, it should be predictable, consistent, and stable under real-world conditions, not just the happy path.&lt;/p&gt;
&lt;h3&gt;The Fluency Illusion&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: Coherent ≠ correct.&lt;/p&gt;
&lt;p&gt;LLMs are excellent at producing coherent, plausible-sounding text. But coherence doesn&apos;t guarantee accuracy, especially for factual or logical tasks. This is what &lt;a href=&quot;https://arxiv.org/abs/2207.05221&quot;&gt;researchers call the &quot;fluency illusion&quot;&lt;/a&gt;, where models generate convincing but incorrect information.&lt;/p&gt;
&lt;p&gt;Common mistakes include trusting the model&apos;s confidence, not fact-checking important information, assuming coherence means correctness, or not verifying logical consistency.&lt;/p&gt;
&lt;p&gt;To avoid this pitfall, always verify important factual claims. Check for logical consistency. Don&apos;t trust the model&apos;s confidence level. And use the model for what it&apos;s good at (generation) while verifying separately.&lt;/p&gt;
&lt;h3&gt;Anthropomorphic Drift&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: Attributing agency where there is none.&lt;/p&gt;
&lt;p&gt;It&apos;s easy to start thinking of the LLM as having intentions, understanding, or agency. When it comes to LLMs and AI, it&apos;s a particularly dangerous cognitive trap because the outputs sound human, even though they result from statistical inference. This is a natural response to fluent language, but it leads to overtrusting the model&apos;s output or falsely assuming it has reasoning abilities. This &lt;a href=&quot;https://www.researchgate.net/publication/340320918_Anthropomorphism_in_AI&quot;&gt;anthropomorphic bias&lt;/a&gt; is well-documented in human-AI interaction research.&lt;/p&gt;
&lt;p&gt;Common mistakes include thinking the model &quot;understands&quot; your intent, attributing reasoning to statistical pattern matching, trusting the model&apos;s &quot;judgment,&quot; or treating the model like a human collaborator.&lt;/p&gt;
&lt;p&gt;To avoid this pitfall, remember that the model is a statistical pattern matcher. Don&apos;t attribute understanding or agency. Verify important outputs independently. And keep the model&apos;s limitations in mind.&lt;/p&gt;
&lt;h3&gt;Prompting as Debugging&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The pitfall&lt;/strong&gt;: Not treating prompt engineering as an experimental, iterative process.&lt;/p&gt;
&lt;p&gt;Prompt engineering is more like debugging than traditional programming. It requires hypothesizing, testing, and refining based on results. This &lt;a href=&quot;https://arxiv.org/abs/2303.07839&quot;&gt;iterative approach&lt;/a&gt; is essential for building reliable AI systems.&lt;/p&gt;
&lt;p&gt;Common mistakes include expecting prompts to work perfectly on the first try, not iterating based on results, not isolating variables when testing, or giving up too quickly when things don&apos;t work.&lt;/p&gt;
&lt;p&gt;To avoid this pitfall, treat prompting as iterative and experimental. Hypothesize, isolate, and refine systematically. Test one change at a time. And learn from failures and unexpected results.&lt;/p&gt;
&lt;h2&gt;Conclusion: Mental Clarity Beats Prompt Tinkering&lt;/h2&gt;
&lt;p&gt;The most important skill in working with LLMs isn&apos;t knowing the latest prompt techniques or having the most sophisticated templates. It&apos;s &lt;strong&gt;thinking clearly&lt;/strong&gt; about what these systems actually are and how they actually work.&lt;/p&gt;
&lt;p&gt;When you understand that LLMs are statistical pattern matchers, not reasoning engines, you write different prompts. When you recognize that coherence doesn&apos;t equal correctness, you build different systems. When you see prompting as declarative programming, you approach it with different tools and practices.&lt;/p&gt;
&lt;p&gt;The clearer you think about LLMs, the more reliable your outcomes will be. Mental models help you reason about these systems. Prompts are interfaces, not dialogues. And the better you understand the cognitive pitfalls that distort your thinking, the more effectively you can avoid them.&lt;/p&gt;
&lt;p&gt;The future of AI development isn&apos;t about writing better prompts; it&apos;s about thinking more clearly about what we&apos;re actually building and how these systems actually work.&lt;/p&gt;
</content:encoded><category>Software Engineering</category></item><item><title>NSFW: Use cases for Bitwise operators in Js</title><link>https://darkotasevski.dev/writing/usecases-for-biwise-web/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/usecases-for-biwise-web/</guid><description>Practical (or not so) use cases for Bitwise operators in Javascript</description><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators&quot;&gt;Bitwise operators&lt;/a&gt; in Javascript are mostly used for numerical conversions/computations, because sometimes they&apos;re much faster than their &lt;code&gt;Math&lt;/code&gt; or &lt;code&gt;parseInt&lt;/code&gt; equivalents. You can take a look at some benchmarks &lt;a href=&quot;https://jsperf.com/math-floor-vs-math-round-vs-parseint/18&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s important to note, as pointed out &lt;a href=&quot;https://j11y.io/javascript/double-bitwise-not/#comment-29617&quot;&gt;by Mathias Bynens&lt;/a&gt;, that bitwise operations only work reliably on numbers that can be expressed as 32-bit sequences. Any numbers above 2147483647 or below -2147483648 will not work as you expect. This is usually acceptable though.&lt;/p&gt;
&lt;p&gt;However, JavaScript numbers are always 64-bit binary floating-point values, following the international IEEE 754 standard. Thus the results of bitwise operators, though computed with 32-bit integer math, are stored in the floating-point form. That works because the range of 32-bit integers fits comfortably and precisely in a 64-bit float.&lt;/p&gt;
&lt;p&gt;But the price you have to pay for that increase in speed is code readability. Since I&apos;ve started coding several years ago, I&apos;ve maybe used bitwise operators once or twice, because most work I don&apos;t have much to do with computation. Nevertheless, I&apos;ve been interested in bitwise operators usage in real-world code for a long time, and I have a gist with collected snippets from all over the internet, just in case I need it sometimes 😄.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;So, here are some excerpts from my collection:&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;| 0&lt;/code&gt; is an easy and fast way to convert anything to integer&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;( 3|0 ) === 3;                 // it does not change integers
( 3.3|0 ) === 3;               // it casts off the fractional part in fractionalal numbers
( 3.8|0 ) === 3;               // it does not round, but exactly casts off the fractional part
( -3.3|0 ) === -3;             // including negative fractional numbers
( -3.8|0 ) === -3;             // which have Math.floor(-3.3) == Math.floor(-3.8) == -4
( &quot;3&quot;|0 ) === 3;               // strings with numbers are typecast to integers
( &quot;3.8&quot;|0 ) === 3;             // during this the fractional part is cast off too
( &quot;-3.8&quot;|0 ) === -3;           // including negative fractional numbers
( NaN|0 ) === 0;               // NaN is typecast to 0
( Infinity|0 ) === 0;          // the typecast to 0 occurs with the Infinity
( -Infinity|0 ) === 0;         // and with -Infinity
( null|0 ) === 0;              // and with null,
( (void 0)|0 ) === 0;          // and with undefined
( []|0 ) === 0;                // and with an empty array
( [3]|0 ) === 3;               // but an array with one number is typecast to number
( [-3.8]|0 ) === -3;           // including the cast off of the fractional part
( [&quot; -3.8 &quot;]|0 ) === -3;       // including the typecast of strings to numbers
( [-3.8, 22]|0 ) === 0         // but an Array with several numbers is typecast to 0
( {}|0 ) === 0;                // an empty object is typecast to 0
( {&apos;2&apos;:&apos;3&apos;}|0 ) === 0;         // or a not empty object
( (function(){})|0 ) === 0;    // an empty function is typecast to 0 too
( (function(){ return 3;})|0 ) === 0;

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Replacements for &lt;code&gt;Math.floor()&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;(~~n)                           
n|n
n&amp;amp;n

// Generate random RGB value:
var r = ~~(Math.random() * 255);

~~null;      // 0
~~undefined; // 0
~~0;         // 0
~~{};        // 0
~~[];        // 0
~~(1/0);     // 0
~~false;     // 0
~~true;      // 1
~~1.2543;    // 1
~~4.9;       // 4
~~(-2.999);  // -2

// An example
const n = Math.PI;   // 3.141592653589793

Math.floor(n);       // 3
parseInt(n, 10);     // 3
~~n; // 3 
n|n; // 3            // n|n and n&amp;amp;n always yield the same results as ~~n
n&amp;amp;n; // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It should be noted that of these last three alternatives, &lt;code&gt;n|n&lt;/code&gt; &lt;a href=&quot;https://jsperf.com/rounding-numbers-down&quot;&gt;appears to be the fastest&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;~~&lt;/code&gt;&apos;s flooring capabilities make it a better alternative to &lt;code&gt;Math.floor&lt;/code&gt; if you know you’re dealing with positives — it’s faster and takes up fewer characters. It’s not quite as readable though.&lt;/p&gt;
&lt;h2&gt;Parsing hexadecimal value to get RGB color values&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;var hex = &apos;ffaadd&apos;;
var rgb = parseInt(hex, 16); // rgb value is 16755421 in decimal = 111111111010101011011101 in binary = total 24 bits


var red   = (rgb &amp;gt;&amp;gt; 16) &amp;amp; 0xFF; // returns 255
var green = (rgb &amp;gt;&amp;gt; 8) &amp;amp; 0xFF;  // returns 170
var blue  = rgb &amp;amp; 0xFF;         // returns 221  

// How is it working:

// There are two bitwise operations as named SHIFTING and AND operations.
// SHIFTING is an operation where the bits are shifted toward a given direction by adding 0 (zero) bit for vacated bit fields.
// AND is an operation that is the same as multiplying in Math. For instance, if the 9th bit of the given first bit-set is 0
// and 9th bit of the given second bit-set is 1, the new value will be 0 because of 0 x 1 = 0 in math.

// 0xFF (000000000000000011111111 in binary) - used for to evaluate only last 8 bits of a given another bit-set by performing bitwise AND (&amp;amp;) operation. 
// The count of bits is 24 and the first 16 bits of 0xFF value consist of zero (0) value. Rest of bit-set consists of one (1) value.
console.log(&quot;0xFF \t\t\t\t: &quot;, 0xFF) 


// 111111111010101011011101 -&amp;gt; bits of rgb variable
// 000000000000000011111111 -&amp;gt; 255 after (rgb &amp;gt;&amp;gt; 16) shifting operation
// 000000000000000011111111 -&amp;gt; 255 complement (changes the first 16 bits and does nothing for the last 8 bits)
// 000000000000000011111111 -&amp;gt; result bits after performing bitwise &amp;amp; operation
console.log(&quot;Red - (rgb &amp;gt;&amp;gt; 16) &amp;amp; 0xFF \t: &quot;, (rgb &amp;gt;&amp;gt; 16) &amp;amp; 0xFF) // used for to evaluate the first 8 bits

// 111111111010101011011101 -&amp;gt; bits of rgb variable
// 000000001111111110101010 -&amp;gt; 65450 -&amp;gt; &apos;ffaa&apos;
// 000000000000000011111111 -&amp;gt; 255 complement (changes the first 16 bits and does nothing for the last 8 bits)
// 000000000000000010101010 -&amp;gt; result bits after performing bitwise &amp;amp; operation
// calculation -&amp;gt; 000000001111111110101010 &amp;amp; 000000000000000011111111 = 000000000000000010101010 = 170 in decimal = &apos;aa&apos; in hex-decimal
console.log(&quot;Green - (rgb &amp;gt;&amp;gt; 8) &amp;amp; 0xFF \t: &quot;, (rgb &amp;gt;&amp;gt; 8) &amp;amp; 0xFF) // used for to evaluate the middle 8 bits 

// 111111111010101011011101 -&amp;gt; &apos;ffaadd&apos;
// 000000000000000011111111 -&amp;gt; 255 complement (changes the first 16 bits and does nothing for the last 8 bits)
// 000000000000000011011101 -&amp;gt; result bits after performing bitwise &amp;amp; operation 
// calculation -&amp;gt; 111111111010101011011101 &amp;amp; 000000000000000011111111 = 221 in decimal = &apos;dd&apos; in hex-decimal
console.log(&quot;Blue - rgb &amp;amp; 0xFF \t\t: &quot;, rgb &amp;amp; 0xFF) // // used for to evaluate the last 8 bits.

console.log(&quot;It means that `FFAADD` hex-decimal value specifies the same color with rgb(255, 170, 221)&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;^&lt;/code&gt; bitwise XOR as a &lt;code&gt;I/O&lt;/code&gt; toggler&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// https://stackoverflow.com/a/22061240/7453363
function toggle(evt) {
  evt.target.IO ^= 1;                                    // Bitwise XOR as 1/0 toggler
  evt.target.textContent = evt.target.IO ? &quot;ON&quot; : &quot;OFF&quot;; // Unleash your ideas
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Check if number is odd:&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;function isOdd(number) {
    return !!(number &amp;amp; 1);
}

isOdd(1); // true, 1 is odd
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Check whether indexOf returned -1 or not&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;var foo = &apos;abc&apos;;
!~foo.indexOf(&apos;bar&apos;); // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Flip a boolean value&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;var foo = 1;
var bar = 0;

foo ^= 1 // 0
bar ^= 1 // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Bitwise operators in JavaScript introduce weird situations where &lt;code&gt;(12 &amp;amp; 9) === 8&lt;/code&gt; and &lt;code&gt;(12 &amp;amp; 3) === 0&lt;/code&gt;, which looks totally out of the place if you don&apos;t understand at first look what&apos;s happening beneath (and the most of the people I know don&apos;t, me included).&lt;/p&gt;
&lt;p&gt;The performance differences, even though they may seem compelling, are entirely negligible in most cases. You should only sacrifice readability for performance if you really need to squeeze out those precious microseconds, and if you do that please leave a comment explaining what is happening, who knows, maybe you&apos;ll need it someday 😄. The only other reason for using these bitwise tricks is to make your code look more complicated than it really is, which is probably a stupid reason. There are also edge cases to watch out for, so you can&apos;t just replace all your current &lt;code&gt;Math.floor()&lt;/code&gt; calls with &lt;code&gt;~~&lt;/code&gt;. Things will likely fail.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/654057/where-would-i-use-a-bitwise-operator-in-javascript&quot;&gt;Where would I use a bitwise operator in JavaScript?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Web dev</category></item><item><title>Why I Care (a Lot) About Fast CI</title><link>https://darkotasevski.dev/writing/why-i-care-about-ci/</link><guid isPermaLink="true">https://darkotasevski.dev/writing/why-i-care-about-ci/</guid><description>Slow CI kills productivity. Here’s how I’ve seen it happen, and what I do about it.</description><pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve never heard an engineer say, “I love our CI.”&lt;br /&gt;
Programming languages, editors, frameworks — sure. CI? Usually silence… or grumbling in Slack.&lt;/p&gt;
&lt;p&gt;For me, the root cause is almost always the same: &lt;strong&gt;slow pipelines&lt;/strong&gt;. And slow CI doesn’t just waste time, it actively changes how engineers work — usually for the worse.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;How Slow CI Hurts Teams&lt;/h2&gt;
&lt;p&gt;I’ve been on projects where a single CI run takes 45+ minutes. The result? People batch up changes into &lt;strong&gt;huge feature branches&lt;/strong&gt; so they “wait less often.” In reality, reviewers now have to sift through massive diffs, making code reviews harder, slower, and less effective.&lt;/p&gt;
&lt;p&gt;Slow CI also kills &lt;strong&gt;continuous improvement&lt;/strong&gt;. Spot a small cleanup opportunity? Rename a variable? Upgrade a dependency? You might think twice if it means an hour-long feedback loop. And when the little fixes stop happening, they pile up — leading to bigger, riskier refactors later.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Signs Your CI Is Too Slow&lt;/h2&gt;
&lt;p&gt;I’ve found these to be the big red flags:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Large, long-lived branches&lt;/strong&gt; becoming the norm.&lt;/li&gt;
&lt;li&gt;Engineers skipping full local test runs because “it takes too long.”&lt;/li&gt;
&lt;li&gt;People only running the specific tests they touched, never the whole suite.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don’t obsess over a magic number for “acceptable build time” — look at these patterns. If they’re happening, your CI speed is below the productivity threshold.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Treat CI Slowness Like a Bug&lt;/h2&gt;
&lt;p&gt;The biggest shift for me was treating slow CI like a &lt;strong&gt;production issue&lt;/strong&gt;.&lt;br /&gt;
It has a cost: wasted engineer time, reduced quality, and growing tech debt. That cost can exceed the impact of some actual user-facing bugs.&lt;/p&gt;
&lt;p&gt;If you’re serious about fixing it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Make it a team goal&lt;/strong&gt; — something measurable, like “All builds under 15 minutes.”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dedicate time to CI maintenance&lt;/strong&gt; — just like you do for refactoring or bug fixing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Give everyone ownership&lt;/strong&gt; — engineers should understand and influence what runs in the pipeline, not just “throw code over the wall” to some invisible process.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;Show Your Tools Some Love&lt;/h2&gt;
&lt;p&gt;We’ll spend hours tweaking our editors or shell configs, but the CI pipeline is the &lt;strong&gt;shared tool&lt;/strong&gt; that every engineer uses daily. It deserves the same level of care — maybe more.&lt;/p&gt;
&lt;p&gt;A fast, well-maintained CI isn’t magic. But it will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Shorten feedback loops.&lt;/li&gt;
&lt;li&gt;Encourage smaller, safer changes.&lt;/li&gt;
&lt;li&gt;Make continuous improvement a habit, not a luxury.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to see how I approach speeding up CI in practice, stay tuned for my follow-up on pipeline tuning for Elixir projects. And if you’re curious about our approach to macOS CI, check out &lt;a href=&quot;/blog/continuous-integration-for-small-ios-macos-teams/&quot;&gt;Continuous Integration for Small iOS/macOS Teams&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>Software Engineering</category></item></channel></rss>