Friday, December 26, 2025

The Five Dysfunctions of Software Teams, Part 1: Absence of Trust

The Five Dysfunctions of Software Teams, Part 1: Absence of Trust

This is the first post in a six-part series exploring The Five Dysfunctions of a Team through the lens of modern software engineering.

I recently read The Five Dysfunctions of a Team because a company I interviewed with said they base their culture on it. I was curious whether it would feel like generic leadership advice or something that actually mapped to real work.

It resonated with me more than I expected, especially as an individual contributor.

Not because the ideas were flashy or new, but because they described patterns I have seen repeatedly in engineering organizations that otherwise look healthy. Smart people. Reasonable processes. Solid intentions. And still, things quietly break down.

The first dysfunction, absence of trust, sits at the base of Lencioni’s model. If it is present, everything above it struggles. In software teams, it often shows up in subtle, normalized ways.

What lack of trust looks like in engineering

On software teams, lack of trust rarely looks like open hostility. It looks like:

  • Engineers avoiding areas of the codebase they do not “own”

  • Over-reliance on one or two people who “know how it works”

  • Pull requests that get approved without meaningful review

  • Bugs fixed in isolation instead of discussed in the open

  • Retrospectives that stay shallow and polite

None of this feels dramatic. That is why it is so dangerous.

How this breaks agile in practice

Agile assumes trust. Not as a nice-to-have, but as a prerequisite.

Consider what agile asks teams to do:

  • Share unfinished work early

  • Inspect and adapt frequently

  • Surface blockers quickly

  • Learn from failure without blame

Without trust, those practices turn into rituals instead of tools.

Daily standups become status reporting instead of problem solving.
Sprint reviews show only safe, polished work.
Retrospectives avoid the real issues because no one wants to look incompetent or difficult.

The process keeps running, but the learning stops.

The myth of the “strong IC”

As individual contributors, many of us learned that being good means being self-sufficient. You debug alone. You figure it out. You do not slow others down with questions.

That mindset feels professional, but it creates brittle teams.

When trust is low:

  • Knowledge stays siloed

  • Onboarding takes forever

  • Bus factor drops to one or two people

  • The team cannot respond well to change

Ironically, the more everyone tries to look strong individually, the weaker the team becomes.

Trust is built through small, visible behaviors

Trust on engineering teams is not built through offsites or trust exercises. It is built in the work.

From an IC perspective, that looks like:

  • Asking questions early, especially basic ones

  • Narrating your thinking in pull requests

  • Admitting when you broke something, quickly and publicly

  • Pairing or mobbing on risky changes

  • Writing down what you learn instead of keeping it in your head

These actions feel vulnerable, especially in cultures that reward speed and certainty. But they are exactly what allow agile practices to work as intended.

Blamelessness is not about being nice

Blameless postmortems are often misunderstood as being soft. They are not.

They are about shifting focus from “who messed up” to “how did the system allow this to happen.”

When teams trust each other:

  • Incidents become learning opportunities

  • Engineers speak honestly about near-misses

  • Fixes address root causes, not just symptoms

Without trust, postmortems become performative or avoided entirely. The same failures repeat under new names.

A practical takeaway for ICs

You do not need to be a manager to improve trust on your team.

Pick one behavior and practice it consistently:

  • Ask the question you think you should already know

  • Say “I do not understand this yet” in a design discussion

  • Call out uncertainty instead of masking it with confidence

Agile is not about moving fast. It is about learning fast.
Learning fast requires trust.
And trust starts with individual contributors being willing to be seen as human.


Up next: Part 2 will look at Fear of Conflict, and how avoiding disagreement in design reviews and architectural discussions quietly erodes software quality.

Series note:
This post is part of a six-part series applying The Five Dysfunctions of a Team to software engineering, with a focus on how these patterns show up in day-to-day work for individual contributors.

If this resonated, the next post explores Fear of Conflict and how avoiding disagreement in design reviews, pull requests, and architectural decisions quietly degrades code quality and team health.

You do not need to be a manager to influence any of this. You just need to notice the patterns and decide how you want to show up in them.

Thursday, December 25, 2025

The Art of Naming: Chapter 2 of Clean Code

 

The Art of Naming: Chapter 2 of Clean Code

After establishing why clean code matters in Chapter 1, Robert C. Martin dives into one of the most fundamental skills in programming: choosing good names. Chapter 2, "Meaningful Names," might seem simple at first, but it contains wisdom that separates amateur code from professional craftsmanship.

Names Are Everywhere

Before we get into the rules, consider this: names are everywhere in software. We name variables, functions, arguments, classes, packages, source files, and directories. We name and rename constantly. Given how much naming we do, we might as well do it well.

Use Intention-Revealing Names

The name of a variable, function, or class should answer three big questions: why it exists, what it does, and how it's used. If a name requires a comment to explain it, the name doesn't reveal its intent.

Consider this example in Ruby:

ruby
d = 10 # elapsed time in days

Versus:

ruby
elapsed_time_in_days = 10

Or in TypeScript:

typescript
const d: number = 10; // elapsed time in days

Versus:

typescript
const elapsedTimeInDays: number = 10;

The difference seems trivial, but it compounds across thousands of lines of code. Good names make code self-documenting.

Avoid Disinformation

Programmers must avoid leaving false clues that obscure the meaning of code. Don't refer to a grouping of accounts as account_list unless it's actually a List or Array. If it's not, account_group or just accounts would be better.

ruby
# Misleading - not actually an array
account_list = Account.where(active: true)

# Better - accurately describes what it is
accounts = Account.where(active: true)
typescript
// Misleading - this is a Set, not an Array
const accountList: Set<Account> = new Set(activeAccounts);

// Better
const accountSet: Set<Account> = new Set(activeAccounts);

Beware of names that vary in small ways. How quickly can you spot the difference between XYZControllerForEfficientHandlingOfStrings and XYZControllerForEfficientStorageOfStrings? These similar names create cognitive load and opportunities for bugs.

Make Meaningful Distinctions

If you have two things that need different names, make sure the names actually convey different meanings. Number-series naming (a1, a2, a3) is the opposite of intentional naming. So is noise words.

What's the difference between ProductInfo and ProductData? Between Customer and CustomerObject? These distinctions are meaningless. Noise words like Info, Data, Object, Manager, Processor don't add clarity, they just add clutter.

ruby
# Meaningless distinction
class ProductInfo
end

class ProductData
end

# Better - use one clear name
class Product
end
typescript
// Noise words that add no meaning
interface CustomerObject {
  name: string;
}

class CustomerManager {
  // What does "Manager" actually do here?
}

// Better - clear and direct
interface Customer {
  name: string;
}

class CustomerRepository {
  // "Repository" is a known pattern
}

Use Pronounceable Names

This might seem obvious, but it makes a huge difference. If you can't pronounce a name, you can't discuss it without sounding like an idiot.

Compare:

ruby
genymdhms = Time.now

With:

ruby
generation_timestamp = Time.now

Or in TypeScript:

typescript
const genymdhms: Date = new Date();

Versus:

typescript
const generationTimestamp: Date = new Date();

Which one can you actually say out loud to a teammate?

Use Searchable Names

Single-letter names and numeric constants have a particular problem: they're nearly impossible to search for. If you're using e as a variable name, try searching for it in a large codebase. Good luck.

ruby
# Hard to search for
users.select { |u| u.age > 5 }

# Better - searchable and meaningful
MIN_ADULT_AGE = 18
users.select { |user| user.age > MIN_ADULT_AGE }
typescript
// Hard to search for
const filtered = users.filter(u => u.age > 5);

// Better
const MIN_ADULT_AGE = 18;
const adultUsers = users.filter(user => user.age > MIN_ADULT_AGE);

Martin suggests that the length of a name should correspond to the size of its scope. If a variable is only used in a small loop, i might be fine. But if it has a larger scope, it needs a more descriptive name.

Avoid Mental Mapping

Readers shouldn't have to mentally translate your names into other names they already know. A single-letter variable name is fine for a loop counter, but using r for the "lowercase version of a URL with the host and scheme removed" forces readers to keep a mental map.

ruby
# Forces mental mapping
r = url.downcase.gsub(/^https?:\/\/[^\/]+/, '')

# Clear and direct
normalized_path = url.downcase.gsub(/^https?:\/\/[^\/]+/, '')

Smart programmers write code that others can understand. Professional programmers write clarity.

Class Names and Method Names

Martin provides clear guidance here:

Classes and objects should have noun or noun phrase names like Customer, WikiPage, Account, or AddressParser. Avoid words like Manager, Processor, Data, or Info in class names. A class name should not be a verb.

ruby
# Good class names
class Customer
end

class OrderProcessor # Sometimes "Processor" is acceptable if it truly processes
end

class PaymentGateway
end
typescript
// Good class names
class Customer {
}

class InvoiceGenerator {
}

interface UserProfile {
}

Methods should have verb or verb phrase names like post_payment, delete_page, or save. In Ruby, follow convention with snake_case for methods. In TypeScript, use camelCase.

ruby
# Good method names
def post_payment
end

def delete_page
end

def calculate_total
end

# Accessors and predicates
def active?
end

def total
end

def total=(value)
end
typescript
// Good method names
function postPayment(): void {
}

function deletePage(): void {
}

function calculateTotal(): number {
}

// Accessors and predicates
function isActive(): boolean {
}

function getTotal(): number {
}

function setTotal(value: number): void {
}

Pick One Word Per Concept

Pick one word for one abstract concept and stick with it. It's confusing to have fetch, retrieve, and get as equivalent methods in different classes. Choose one and use it consistently.

ruby
# Inconsistent - pick one!
class UserRepository
  def fetch_by_id(id)
  end
end

class OrderRepository
  def retrieve_by_id(id)
  end
end

class ProductRepository
  def get_by_id(id)
  end
end

# Better - consistent vocabulary
class UserRepository
  def find_by_id(id)
  end
end

class OrderRepository
  def find_by_id(id)
  end
end

class ProductRepository
  def find_by_id(id)
  end
end

Similarly, don't use the same word for two purposes. If you have add methods that create a new value by adding two values, don't use add for a method that puts a single value into a collection. Use append or push instead.

ruby
# Confusing - "add" used for different purposes
def add(a, b)
  a + b
end

def add(item)
  @items << item
end

# Better - distinct names for distinct operations
def sum(a, b)
  a + b
end

def append(item)
  @items << item
end

Use Solution Domain Names

Remember that the people reading your code are programmers. Go ahead and use computer science terms, algorithm names, pattern names, math terms. The name AccountVisitor means something to a programmer familiar with the Visitor pattern. Use technical names when appropriate.

ruby
class OrderDecorator
  # "Decorator" is a known pattern
end

class UserFactory
  # "Factory" is a known pattern
end

class EventObserver
  # "Observer" is a known pattern
end
typescript
class CacheStrategy {
  // "Strategy" is a known pattern
}

class DatabaseAdapter {
  // "Adapter" is a known pattern
}

class CommandQueue {
  // "Queue" is a known data structure
}

Use Problem Domain Names

When there's no programmer-ese for what you're doing, use the name from the problem domain. At least the programmer who maintains your code can ask a domain expert what it means.

ruby
# Healthcare domain
class PatientAdmission
end

class DiagnosisCode
end

# Financial domain
class LedgerEntry
end

class ReconciliationReport
end

Add Meaningful Context

Imagine you see variables named first_name, last_name, street, city, state, and zipcode. You can infer they're part of an address. But what if you just see the variable state in a method? Adding context helps: addr_state is better, but creating an Address class is best.

ruby
# Context through grouping
class Address
  attr_accessor :street, :city, :state, :zipcode
end

# Now it's clear what "state" means
address = Address.new
address.state = "MN"
typescript
// Context through typing
interface Address {
  street: string;
  city: string;
  state: string;
  zipcode: string;
}

// Clear context
const userAddress: Address = {
  street: "123 Main St",
  city: "Minneapolis",
  state: "MN",
  zipcode: "55401"
};

Don't add gratuitous context though. If you're building a "Gas Station Deluxe" application, prefixing every class with GSD is overkill. Shorter names are generally better than longer ones, as long as they're clear.

The Hardest Thing in Programming

Phil Karlton famously said there are only two hard things in Computer Science: cache invalidation and naming things. Chapter 2 of Clean Code won't make naming easy, but it provides a framework for making better naming decisions.

Key Takeaways

  1. Choose names that reveal intent and make code self-documenting
  2. Avoid misleading names and meaningless distinctions
  3. Make names pronounceable and searchable
  4. Use consistent vocabulary throughout your codebase
  5. Class names should be nouns, method names should be verbs
  6. Pick one word per concept and stick with it
  7. Use technical terms when appropriate, domain terms when not
  8. Add context through class structure, not prefixes

Practical Application

The next time you write code, pause before naming something. Ask yourself: will another developer (or future you) understand what this is without reading the implementation? If the answer is no, take a moment to find a better name. It's an investment that pays dividends every time someone reads that code.


What naming conventions have you found most helpful in your projects? Share your thoughts below!

The Five Dysfunctions of Software Teams, Part 1: Absence of Trust

The Five Dysfunctions of Software Teams, Part 1: Absence of Trust This is the first post in a six-part series exploring The Five Dysfunctio...