<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Samuel Thuku</title>
    <description>The latest articles on DEV Community by Samuel Thuku (@samthuku).</description>
    <link>https://dev.to/samthuku</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3718513%2Fcdd3751d-640e-4630-ad24-fb7469711ea3.png</url>
      <title>DEV Community: Samuel Thuku</title>
      <link>https://dev.to/samthuku</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/samthuku"/>
    <language>en</language>
    <item>
      <title>Stop Solving Solved Problems: Escaping the Cycle of Duplicated Code</title>
      <dc:creator>Samuel Thuku</dc:creator>
      <pubDate>Wed, 11 Mar 2026 07:55:54 +0000</pubDate>
      <link>https://dev.to/samthuku/stop-solving-solved-problems-escaping-the-cycle-of-duplicated-code-3bfa</link>
      <guid>https://dev.to/samthuku/stop-solving-solved-problems-escaping-the-cycle-of-duplicated-code-3bfa</guid>
      <description>&lt;p&gt;It’s a scenario that plays out in countless offices and home offices every day: a software developer stares at a tricky problem, cracks their knuckles, and begins architecting a solution in their mind. They envision the modules, the APIs, and the elegant lines of code that will slay the dragon. Days, or even weeks, later they emerge with a working solution, proud of their creation, only to discover (or have a colleague point out) that an open-source library or internal tool already does the exact same thing.&lt;/p&gt;

&lt;p&gt;Sometimes the existing tool is even better than what the developer built.&lt;/p&gt;

&lt;p&gt;This phenomenon is the source of a well-worn joke in the industry: “Every time a developer encounters a problem, they start building a solution, completely missing that it already exists.” While it’s funny, the reality behind it is a costly and frustrating cycle of duplicated effort. It’s a multifaceted issue rooted in psychology, corporate culture, and sometimes a lack of training. It’s often summarized by the pejorative term “Not Invented Here” (NIH) syndrome.&lt;/p&gt;

&lt;p&gt;The Case for “Proudly Found Elsewhere”&lt;/p&gt;

&lt;p&gt;The cost of this duplication is staggering. It’s not just wasted development hours. It’s the compounded cost of maintaining multiple, slightly different implementations of the same thing. Every bug fix has to be applied in multiple places. Every new developer has to learn multiple systems. Over time, the software ecosystem becomes bloated and brittle.&lt;/p&gt;

&lt;p&gt;This is why a cultural shift toward “Proudly Found Elsewhere” is so important. It’s a mindset that prioritizes solving the business problem over the personal satisfaction of writing every line of code. It recognizes that standing on the shoulders of giants, whether they are open-source maintainers or a team in the next building, leads to faster delivery, higher quality, and more sustainable software.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Build a “Proudly Found Elsewhere” Culture
&lt;/h2&gt;

&lt;p&gt;Moving past NIH syndrome requires a conscious effort from both individuals and organizations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For organizations: Build a marketplace, not a black hole.&lt;/strong&gt;&lt;br&gt;
If you want code to be reused, you have to make it easy to find and easy to use. This means investing in internal component marketplaces where teams can publish their libraries with clear documentation, usage examples, and versioning. It also means fostering a culture of collaboration where teams are encouraged to talk to each other about their technical challenges and share solutions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For organizations: Incentivize reuse.&lt;/strong&gt;&lt;br&gt;
Change the metrics. Reward engineers who contribute to internal libraries and those who choose to reuse them. If a project is delivered faster and with fewer bugs because it leveraged existing components, that should be celebrated as a win, not dismissed as “not doing real work.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For developers: Ask “Why not?” before “How?”&lt;/strong&gt;&lt;br&gt;
Before writing a single line of code for a common problem like logging, authentication, or data validation, make it a habit to search for existing solutions. Ask your teammates. Check the company wiki. Look for open-source libraries. Assume the solution already exists until you can prove that it doesn’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For developers: Contribute, don’t fork.&lt;/strong&gt;&lt;br&gt;
If you find an existing library that covers 80% of what you need, don’t immediately decide to build the remaining 20% from scratch. Consider contributing the missing feature back to the original project. Another option is extending it with a plugin. Improving an existing tool is almost always better than creating a new one that will have to be maintained forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Nuance: When Reinvention Is the Right Choice
&lt;/h2&gt;

&lt;p&gt;It’s important to acknowledge that reinventing the wheel isn’t always a bad thing. Sometimes it leads to innovation. As developer Lea Verou has argued, using a massive, bloated library for a tiny fraction of its features can introduce significant performance overhead and long-term maintenance costs. If you only need 5% of a library’s functionality, writing a small, purpose-built function might actually be the smarter choice.&lt;/p&gt;

&lt;p&gt;For example, parsing a few lines of simple Markdown for bold text and links doesn’t necessarily require a 1,700-line library. A small custom function might do the job more efficiently and with far less complexity.&lt;/p&gt;

&lt;p&gt;The key is intentionality. The goal is to stop the accidental or arrogant reinvention of wheels, the kind that happens because a developer didn’t look, didn’t ask, or didn’t care. By shifting our focus from the pride of creation to the satisfaction of solving real problems efficiently, we can escape this costly cycle.&lt;/p&gt;

&lt;p&gt;We should celebrate finding the perfect existing solution just as much as we celebrate building a new one from scratch.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>softwareengineering</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
    <item>
      <title>My Brain on Concurrency: Goroutines, Mutexes, and a Coworking Space Analogy</title>
      <dc:creator>Samuel Thuku</dc:creator>
      <pubDate>Thu, 05 Mar 2026 00:09:14 +0000</pubDate>
      <link>https://dev.to/samthuku/my-brain-on-concurrency-goroutines-mutexes-and-a-coworking-space-analogy-4hf4</link>
      <guid>https://dev.to/samthuku/my-brain-on-concurrency-goroutines-mutexes-and-a-coworking-space-analogy-4hf4</guid>
      <description>&lt;p&gt;For the past week, I've been deep in the weeds of Go. I've built a few REST APIs and CLI tools, so I understand the syntax fairly well. But there is a pillar of Go that I kept treating like a black box: Concurrency.&lt;/p&gt;

&lt;p&gt;I understood the value of it, making programs fast by doing multiple things at once but I didn't understand the mechanics.I mapped it to something I deal with every day, a busy coworking space.&lt;/p&gt;

&lt;p&gt;Here is what I learned about Goroutines, Mutexes, and the often overlooked RWMutex, reframed through the lens of freelancers fighting over resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The workers: Understanding Goroutines
&lt;/h2&gt;

&lt;p&gt;Imagine a single-threaded program as a co-working space with just one person working there. They have the whole place to themselves. They can spread out, take calls, and use the whiteboard without interruption. It's peaceful, but if they get stuck waiting for a client to email them back, the entire office goes idle.&lt;/p&gt;

&lt;p&gt;Goroutines are like inviting more freelancers into that space.&lt;/p&gt;

&lt;p&gt;Now you have multiple people working on their own projects independently. One is designing a logo, another is writing a blog post, a third is on a sales call. They are all making progress simultaneously. The beauty of Go is that it doesn't necessarily need a separate physical room (OS thread) for each freelancer. The Go runtime acts like a hyper-efficient community manager, shuffling the freelancers onto available desks as needed, allowing thousands of them to coexist without renting a skyscraper.&lt;/p&gt;

&lt;p&gt;Where you actually use this: Think about a web server. In languages like Python or Ruby, each incoming user request typically takes up an entire OS thread, which is heavy and limits how many users you can handle. In Go, each request is handed off to a lightweight Goroutine. This is why Go is so popular for building high-throughput APIs. When thousands of people hit your app at the same time, you're not crashing—you're just activating more freelancers.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Problem: The Resource Contention
&lt;/h2&gt;

&lt;p&gt;But hiring more freelancers creates a new problem. What happens when multiple freelancers need to use the same shared resource?&lt;/p&gt;

&lt;p&gt;In my coworking space analogy, imagine there is only one premium conference room with a video setup. If two freelancers have back-to-back client calls booked at the exact same time, we have a conflict.&lt;/p&gt;

&lt;p&gt;I wrote a small program to simulate this. I imagined a booking system for that conference room. Two Goroutines (freelancers, Alice and Bob) check the calendar simultaneously. They both see that the 10 AM slot is free. They both book it. Suddenly, we have double-booking chaos. Two clients show up to the same meeting link, and the freelancers look unprofessional.&lt;/p&gt;

&lt;p&gt;This is a Race Condition. Both processes were able to read the calendar at the same time, assumed the resource was available, and acted on that stale information.&lt;/p&gt;

&lt;p&gt;Where you actually see this: This exact scenario happens in e-commerce during flash sales. Imagine 100 people trying to buy the last pair of sneakers. Without protection, your inventory system checks stock for all 100 at once, sees "1 available," and approves every purchase. You've now oversold inventory and have 99 angry customers. The race condition isn't just a coding error, it's a financial disaster.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Receptionist: Introducing Mutexes
&lt;/h2&gt;

&lt;p&gt;This is where I learned about Mutexes (Mutual Exclusion Locks). A Mutex is like a receptionist at the front desk.&lt;/p&gt;

&lt;p&gt;The rules are simple:&lt;br&gt;
  &lt;strong&gt;The Lock:&lt;/strong&gt; When a freelancer wants to check or modify the conference room calendar, they must go to the receptionist and ask for the physical booking logbook. The receptionist hands it over and tells everyone else to wait.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Work:&lt;/strong&gt; The freelancer can now safely look at the calendar, see the free slot, and write their name down. They know no one else can sneakily modify it while they are writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Unlock:&lt;/strong&gt; When they are done, they hand the logbook back to the receptionist, who can then give it to the next freelancer waiting in line.&lt;/p&gt;

&lt;p&gt;I refactored my program to introduce a receptionist (the Mutex). By ensuring that only one freelancer could hold the logbook at a time, the chaos stopped.&lt;/p&gt;

&lt;p&gt;The output finally made sense. Alice grabbed the logbook, checked the calendar, booked her slot, and handed it back. Only then did Bob receive the logbook. When Bob looked at the calendar, the 10 AM slot was clearly marked as taken, so he booked the 11 AM slot instead. No double-booking, no chaos.&lt;/p&gt;

&lt;p&gt;Where you actually use this: Any time you have a shared resource that needs to stay consistent. This could be a bank account ledger (ensuring two withdrawals don't overdraft you), a connection pool to a database (ensuring two Goroutines don't grab the same connection), or a simple in-memory cache (Go maps will actually crash if one Goroutine tries to read while another writes). Mutexes are the gatekeepers for your critical data.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Optimization: The RWMutex (Reader/Writer Mutex)
&lt;/h2&gt;

&lt;p&gt;But then I ran into a performance problem. My receptionist (the standard Mutex) was doing their job too well.&lt;/p&gt;

&lt;p&gt;In my co-working space, we added a bulletin board next to the reception desk. It lists all the upcoming community events. This board is read by dozens of freelancers every hour, but it is only updated once a week by the community manager.&lt;/p&gt;

&lt;p&gt;Under my current receptionist rules, if someone wants to read the bulletin board, they have to take the logbook and lock everyone else out. This means if one person is slowly reading the events, twenty other people can't even glance at it. Worse, if someone is reading it, the community manager can't post the weekly update. Everything is blocked for everyone, even for simple look-ups. My program was running slowly because read operations were queuing up behind each other for no reason.&lt;/p&gt;

&lt;p&gt;This is where I discovered the RWMutex (Read-Write Mutex).&lt;/p&gt;

&lt;p&gt;The RWMutex is a smarter receptionist with different rules for readers and writers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple Readers:&lt;/strong&gt; If a freelancer just wants to look at the bulletin board (a read operation), the receptionist lets them look simultaneously. Ten people can read the board at once without blocking each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusive Writer:&lt;/strong&gt; However, if the community manager needs to update the board (a write operation), the receptionist becomes strict. They wait for all current readers to finish, then lock the board exclusively. No one can read while the manager is updating, ensuring no one sees a half-finished, inconsistent version of the board.&lt;/p&gt;

&lt;p&gt;When I refactored my code to use an RWMutex, I saw a huge performance boost. For data that was read frequently but written to rarely, the RWMutex allowed hundreds of Goroutines to access it simultaneously. The system only slowed down during the occasional write, which is exactly what I wanted.&lt;/p&gt;

&lt;p&gt;Where you actually use this: Think about a configuration service for a large application. Thousands of microservices might be reading the configuration values every second to know which database to connect to. However, a developer updates the configuration maybe once a day. Using a standard Mutex here would be massive overkill—readers would be blocked waiting for other readers. An RWMutex allows all those read operations to happen in parallel, keeping the system fast while still protecting the occasional write.&lt;br&gt;
Key Takeaways from My Journey&lt;/p&gt;

&lt;p&gt;If you are just starting with Go concurrency, here are the mental notes I wish I had:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goroutines are like freelancers:&lt;/strong&gt; They allow your program to handle thousands of tasks at once, which is why Go excels at web servers and data pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Race conditions are like double-booking:&lt;/strong&gt; They lead to corrupted data, oversold inventory, and inaccurate balances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutexes are the receptionist:&lt;/strong&gt; They ensure that only one task can modify a critical resource at a time, keeping your data safe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RWMutex is a smarter receptionist:&lt;/strong&gt; It lets multiple readers share the resource for better performance, perfect for configuration data or any read-heavy workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the Race Detector:&lt;/strong&gt; Go's built-in race detector acts like a security camera. It showed me exactly where my double-booking was happening, saving me hours of debugging.&lt;/p&gt;

&lt;p&gt;Concurrency is powerful, but it forces you to think about your program not as a linear script, but as a living system of independent actors competing for shared resources. It's a paradigm shift, but getting these concepts to click was incredibly satisfying.&lt;/p&gt;

&lt;p&gt;_&lt;br&gt;
Thuku Samuel is a software engineer passionate about clean code and Go programming._&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>go</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Calculator in Go: A Masterclass in Software Engineering Best Practices</title>
      <dc:creator>Samuel Thuku</dc:creator>
      <pubDate>Mon, 23 Feb 2026 12:06:05 +0000</pubDate>
      <link>https://dev.to/samthuku/building-a-calculator-in-go-a-masterclass-in-software-engineering-best-practices-4fo2</link>
      <guid>https://dev.to/samthuku/building-a-calculator-in-go-a-masterclass-in-software-engineering-best-practices-4fo2</guid>
      <description>&lt;p&gt;When I set out to build a calculator in Go, I thought it would be a weekend project. After all, how complex could a calculator be?&lt;/p&gt;

&lt;p&gt;I could not have been more wrong.&lt;/p&gt;

&lt;p&gt;What started as a simple calculator evolved into a complete software engineering bootcamp. From requirements to architecture, from testing to security, this tiny project taught me more about professional development than many large-scale systems I have worked on.&lt;br&gt;
The Humble Calculator: More Complex Than You Think&lt;/p&gt;

&lt;p&gt;Before writing code, I asked myself: what does a professional calculator actually need?&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Basic arithmetic and advanced functions

A responsive graphical interface

A command-line interface for power users

Calculation history

Cross-platform compatibility

Comprehensive tests

A secure API layer serving both interfaces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This became my requirements document, forcing me to think about edge cases and constraints before coding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 1:&lt;/strong&gt; Even small projects benefit from written requirements.&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture: Layering Like an Onion
&lt;/h2&gt;

&lt;p&gt;I chose a clean layered architecture:&lt;br&gt;
text&lt;/p&gt;

&lt;p&gt;calculator/&lt;br&gt;
├── cmd/&lt;br&gt;
│   ├── cli/              # CLI entry point&lt;br&gt;
│   └── gui/              # GUI entry point&lt;br&gt;
├── docs/&lt;br&gt;
├── internal/&lt;br&gt;
│   ├── core/              # Core logic&lt;br&gt;
│   ├── parser/            # Expression evaluation&lt;br&gt;
│   ├── service/           # Tokenization&lt;br&gt;
│   ├── ui/                # Fyne interface&lt;br&gt;
│   └── api/               # API layer&lt;br&gt;
├── go.mod&lt;br&gt;
├── go.sum&lt;br&gt;
└── Makefile&lt;/p&gt;

&lt;p&gt;Each layer has a single responsibility. The core knows nothing about the interfaces. The parser evaluates expressions independently. The API layer orchestrates everything securely for both the GUI and CLI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 2:&lt;/strong&gt; Clean architecture benefits projects of any size.&lt;/p&gt;
&lt;h2&gt;
  
  
  The API Layer: Building a Secure Foundation
&lt;/h2&gt;

&lt;p&gt;The most important decision was creating an API layer between the interfaces and business logic. This single abstraction transformed my application.&lt;/p&gt;

&lt;p&gt;Both interfaces share the exact same underlying logic. But simplicity does not mean sacrificing security. A production-ready API needs multiple layers of protection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input validation comes first.&lt;/strong&gt; Every expression is checked for empty values, length limits prevent denial of service attacks, and a whitelist ensures only valid characters are accepted. No unexpected symbols, no control characters, nothing that could be used for injection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structural validation follows.&lt;/strong&gt; The API checks for consecutive operators and ensures parentheses are properly balanced. Only well-formed expressions reach the calculation engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Panic recovery wraps everything.&lt;/strong&gt; Despite best efforts, bugs happen. The API catches any panics, logs them, and returns friendly errors instead of crashing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit logging tracks every request.&lt;/strong&gt; Timestamps and expressions are logged for debugging, performance monitoring, and security forensics. This proved invaluable when users reported unexpected behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standardized responses ensure consistency.&lt;/strong&gt; Every calculation returns the same structure with result, success indicator, and execution time. Both the GUI and CLI parse the same response format.&lt;/p&gt;

&lt;p&gt;All this complexity remains invisible to both interfaces. The API stays simple while the implementation handles the hard parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 3:&lt;/strong&gt; Security is built in from day one, not added later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 4:&lt;/strong&gt; Good APIs abstract complexity behind simple interfaces.&lt;br&gt;
The Beauty of Shared Logic&lt;/p&gt;

&lt;p&gt;With the API layer in place, both interfaces enjoy several benefits automatically.&lt;/p&gt;

&lt;p&gt;The GUI gets a responsive experience with proper error handling. The CLI gets the same reliability for scripting and automation. When I fixed a bug in the parser, both interfaces benefited immediately. When I added security validation, both interfaces became more secure without any changes to their code.&lt;/p&gt;

&lt;p&gt;This is the power of the API layer. It becomes the single source of truth for calculation logic, and every interface simply plugs into it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 5:&lt;/strong&gt; Build once, use everywhere.&lt;br&gt;
The History Feature: Ring Buffers&lt;/p&gt;

&lt;p&gt;Displaying the last 3 operations required a fixed-size history. Go's container/ring provided the perfect solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go

var operationHistory = ring.New(3)

func addToHistory(expression string, result float64) {
    opString := fmt.Sprintf("%s = %v", expression, result)
    operationHistory.Value = opString
    operationHistory = operationHistory.Next()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When users press equals, operations are added to the ring, automatically overwriting the oldest entry. The GUI displays this history visually, while the CLI could easily add a history command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 6:&lt;/strong&gt; Know your standard library.&lt;br&gt;
The UI Challenge: Custom Button Colors&lt;/p&gt;

&lt;p&gt;Fyne is a delightful UI toolkit, but customizing button colors required creativity. The solution stacked colored rectangles behind transparent buttons:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go

btn := func(label string, textColor color.Color, bgColor color.Color, tapped func()) fyne.CanvasObject {
    txt := canvas.NewText(label, textColor)
    txt.TextStyle = fyne.TextStyle{Bold: true}

    bg := canvas.NewRectangle(bgColor)
    button := widget.NewButton("", tapped)
    button.Importance = widget.LowImportance

    content := container.NewStack(bg, button, txt)
    return withHeight(content, 75)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Number buttons became blue, operators orange, and clear red.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 7:&lt;/strong&gt; Understanding composition lets you break free from default constraints.&lt;br&gt;
Testing Through the API&lt;/p&gt;

&lt;p&gt;Testing through the public API exercises the entire flow in one go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go

func TestAddition(t *testing.T) {
    result, err := api.Calculate("2+2")
    if err != nil || result != 4 {
        t.Errorf("Expected 4, got %v", result)
    }
}

func TestDivisionByZero(t *testing.T) {
    _, err := api.Calculate("5/0")
    if err == nil {
        t.Errorf("Expected error for division by zero")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I refactored internal code, tests caught regressions immediately. Because the API serves both interfaces, testing it once covers both the GUI and CLI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 8:&lt;/strong&gt; Test through your public API.&lt;/p&gt;

&lt;p&gt;Error Handling: Graceful Failure&lt;/p&gt;

&lt;p&gt;A calculator that crashes on invalid input is useless. Every edge case was anticipated:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Division by zero returns an error

Invalid decimals are handled

Consecutive operators are validated

Empty calculations default to zero
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The calculator state remains consistent even after errors, whether accessed through the GUI or CLI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 9:&lt;/strong&gt; Users will find creative ways to break your software. Handle it gracefully.&lt;br&gt;
The Development Lifecycle&lt;/p&gt;

&lt;p&gt;This project followed the full software lifecycle:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requirements gathering

Architecture design

API-first implementation

Building both interfaces against the same API

Testing and refactoring

Documentation

Release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Each phase taught something valuable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 10:&lt;/strong&gt; Building software is about understanding problems, not just writing code.&lt;br&gt;
The Big Picture&lt;/p&gt;

&lt;p&gt;The API layer transformed my calculator from a single application into a platform serving both a graphical interface and a command-line interface. It enables consistent behavior across interfaces, comprehensive testing, and easy addition of future clients. Each layer does one thing well, and they all work together seamlessly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 11:&lt;/strong&gt; Think platform, not application.&lt;br&gt;
Key Takeaways&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start with requirements to prevent scope creep

Architect for change with clean separation of concerns

Build an API layer first even with multiple interfaces

Design for multiple interfaces so business logic remains interface-agnostic

Build once, use everywhere by sharing logic through the API

Know your standard library for perfect data structures

Understand UI composition to break free from defaults

Test through your public API for fearless refactoring

Handle errors gracefully because users break things

Document decisions for your future self

Embrace the full lifecycle as each phase teaches something

Keep APIs simple while implementation handles complexity

Build security in from day one with validation, recovery, and audit trails

Think platform, not application for maximum re-usability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A calculator seems simple, but building one properly touches on every aspect of software engineering. It is the perfect project for learning industry best practices without overwhelming complexity.&lt;/p&gt;

&lt;p&gt;The API layer was the unexpected hero. It forced me to think about my software as a platform serving multiple interfaces, made security foundational, simplified testing, and kept both the GUI and CLI beautifully ignorant of underlying complexity.&lt;/p&gt;

&lt;p&gt;If you want to level up your skills, build something simple the right way. Start with requirements, design the API first, then build your interfaces against it. You will be surprised how much you learn.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Happy coding!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thuku Samuel is a software engineer passionate about clean code and Go programming.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>discuss</category>
      <category>architecture</category>
      <category>go</category>
    </item>
  </channel>
</rss>
