Naming as Persona: The Value and Practice of Prudent Code Naming

Date: July 4, 2021 Author: Hasan Topic: Software Engineering
Table of Contents

Abstract

In the process of software development, naming serves as the foundation for code readability, maintainability, and robustness. Inappropriate naming not only increases the cognitive load required to understand the code but can also obscure potential errors, leading to significant maintenance costs throughout the software lifecycle. This article aims to explore the necessity of deliberate naming in software development. Drawing upon software engineering theory and practical experience, it proposes a core set of principles and specific practical recommendations. The paper focuses on key dimensions such as "Integrity," "Explicitness," "Conciseness," and "Systemicity," designed to help developers build clearer, more intuitive, and easier-to-maintain codebases.

Keywords: Software Naming; Code Readability; Code Maintenance; Software Engineering; Programming Practice


1. Introduction

In the discipline of software engineering, code quality directly impacts a project's long-term sustainability. Naming, the most fundamental yet critical element of code, holds significance comparable to an individual’s identity. As stated in Clean Code, good naming is the paramount principle for writing high-quality code. A carefully designed name clearly conveys the code’s intent, often lending the code an inherent aesthetic rhythm; conversely, arbitrary naming can render the code opaque, increasing the maintainer's cognitive burden, slashing development efficiency, and inadvertently concealing potential defects with long-lasting negative consequences.

Research on Extreme Programming emphasizes that, especially for projects with longer lifecycles, poor naming conventions can lead to an exponential increase in maintenance costs. Furthermore, data suggests that approximately 70% of code refactoring efforts are related to naming optimization, underscoring the central role of naming in software engineering.

Given this, this article posits that dedicating time and mental effort to deliberate naming is indispensable when writing critical code. This not only facilitates team collaboration but also saves time for the developer themselves in subsequent maintenance. We will combine theoretical analysis with practical experience to delve into the key principles and methods for software naming.

2. Core Naming Principles

Through the analysis of numerous codebases and the synthesis of software development practices, we have distilled the following core naming principles:

2.1 Principle of Integrity

"Integrity" is the most crucial principle in the art of naming, fundamentally ensuring that the name accurately and completely reflects the intent and function of the named object. Effective Java stresses that names must accurately convey the abstraction they represent, warning against "bait-and-switch" naming—where a function’s declared purpose does not match its actual execution. This mismatch easily misleads maintainers, causing preconceived notions that could lead them to overlook crucial details or even potential errors in the code.

Empirical studies indicate that common violations of integrity include:

2.1.1 Mismatch Between Function and Name

When a function executes logic A but is named B. This often stems from casual initial naming or overly complex function logic, making it difficult to find an appropriate name, resulting in an arbitrary assignment. The former reflects a lack of diligence; the latter suggests a need for code structure optimization, where the logic should be broken down into smaller, clearly named units.

// Bad Practice: Name does not reflect actual function
func validateAndStore(data *UserData) bool {
    // Perform validation
    if !isValid(data) {
        return false
    }

    // Store data (not reflected in the function name)
    db.Store(data)

    // Send notification (not reflected in the function name)
    notificationService.Send(data.UserID)

    return true
}

// Good Practice: Name fully reflects function
func validateStoreAndNotify(data *UserData) bool {
    // Implementation is the same as above
}

2.1.2 Obscured Side Effects

The function name fails to indicate its side effects. Code Complete warns that this is a particularly dangerous form of naming misrepresentation. For instance, a function named check that, besides the check, silently modifies the object’s state:

func (c *Company) check(p *Person) {
    if p.age < 0 {
        panic("age can't be negative")
    }

    c.p = p  // Obscured side effect (mutating internal state)
}

If side effects are involved, the name should be something like checkAndSet() to clearly reflect the behavior. Similarly, code that appears to be a pure function but modifies global state also violates the Integrity Principle.

2.1.3 Names Static Despite Code Evolution

Code logic changes, but its name or comments fail to sync. This includes adding new logic, rewriting features, or completely changing the code's purpose while retaining the old name. Empirical evidence shows that obsolete comments are a common symptom of this issue.

// Pseudocode Example
// Original Comment: Dumps content to a file
func (c *Content) dumpToFile() error {
    // Original code
    f, _ := os.Open(filename)
    f.Write()

    // New code added, but function name or comment not updated
    db, _ = getDb()
    db.Add()  // Also writes to the database
}

2.1.4 Abuse of Conventional Names

In specific contexts, certain names carry conventionally established meanings. Discussions on the design philosophy of the Go language note that the casual appropriation of such names leads to severe semantic confusion. For example, Go's Context should not be casually repurposed; a more distinguishing name (e.g., JobContext) should be used to prevent confusion.

2.2 Principle of Explicitness

During code implementation, developers often make design decisions based on immediate context, which tends to dissipate over time. If naming or logic is too obscure, indirect, or counter-intuitive, it may be clear during writing but difficult for future maintainers to grasp.

The Practice of Programming suggests using a more explicit approach to solve this:

2.2.1 Clear Conditional Logic

Avoid using cryptic negative logic or indirect condition checks. The meaning of a condition can be clarified by introducing a Boolean variable with an explanatory name:

// Bad Practice: Obscure conditional logic
func (p *Project) Get(req *GetRequest) (*GetResponse, error) {
    if req.names == nil {
        resp := &GetResponse{}
        resp.Teacher = &Teacher{}
        return resp, nil
    }

    // Other logic
}

// Good Practice: Explicit conditional logic
func (p *Project) Get(req *GetRequest) (*GetResponse, error) {
    isAllStudentsQuery := (req.names == nil)
    if isAllStudentsQuery {
        resp := &GetResponse{}
        resp.Teacher = &Teacher{}
        return resp, nil
    }

    // Other logic
}

2.2.2 Naming Anonymous Functions

For longer anonymous functions, assign them to a variable with a descriptive name, using the variable name to express the function's intent:

// Bad Practice: Complex anonymous function
students.Filter(func(s *Student) bool {
    return s.Age >= 18 && s.Credits >= 120 && !s.HasGraduated
})

// Good Practice: Naming the function variable
isEligibleForGraduation := func(s *Student) bool {
    return s.Age >= 18 && s.Credits >= 120 && !s.HasGraduated
}
students.Filter(isEligibleForGraduation)

By consistently viewing the code from the perspective of others (or future self), eliminating dependence on implicit context is key to writing intuitive, minimally commented code. Furthermore, inviting colleagues who understand the design but not the implementation details for code review is an effective way to remove implicit dependencies.

2.3 Principle of Conciseness

Overly long names increase cognitive load. Research suggests that names usually exceeding three words may not significantly enhance semantic clarity. Concise naming can be achieved by effectively utilizing the program structure to eliminate information redundancy.

2.3.1 Using Contextual Scope to Eliminate Redundancy

The package name (or namespace) and class name inherently carry partial information. When the package name is student, the Student in student.NewStudent() can often be omitted, becoming student.New(), as the package name implies the specific type:

// Bad Practice: Redundant package name and function name
package student

type Student struct {}

func NewStudent() (*Student, error) {
    // Implementation omitted
}

// Call site: student.NewStudent()

// Good Practice: Using package name to eliminate redundancy
package student

type Student struct {}

func New() (*Student, error) {
    // Implementation omitted
}

// Call site: student.New()

Similarly, the naming of nodes in a tree structure can be simplified, with their full meaning contextually defined by their path within the tree.

2.3.2 Using Parameter Names to Eliminate Redundancy

Function parameter names already indicate the object being processed, eliminating the need to repeat it in the function name:

// Bad Practice: Function name duplicates parameter type information
func handleStudent(student *Student) {
    // Implementation omitted
}

// Good Practice: Using parameter name to eliminate redundancy
func handle(student *Student) {
    // Implementation omitted
}

2.3.3 Shorter Names for Tighter Scopes

Local variables and loop iterators, due to their limited scope, have a lower likelihood of naming conflicts, thus allowing shorter names:

// Appropriately short local variable name
for i, s := range students {
    fmt.Println(i, s.Name)
}

// Appropriately short function parameter name (limited to small functions)
sort.Slice(students, func(i, j int) bool {
    return students[i].Name < students[j].Name
})

It is crucial that conciseness is pursued without introducing ambiguity. If simplified naming misleads the caller (e.g., student.New() could be mistaken for creating a Student type), a more explicit name (e.g., student.NewManager()) should be used:

package student

type Student struct {}
type StudentManager struct {}

// Bad Practice: Overly concise leading to ambiguity
func New() (*StudentManager, error) {
    // Implementation omitted
}
// Call site: student.New() is easily misunderstood as returning Student type

// Good Practice: Naming to avoid ambiguity
func NewManager() (*StudentManager, error) {
    // Implementation omitted
}
// Call site: student.NewManager() clearly returns StudentManager type

2.4 Principle of Systemicity

Naming is not an isolated activity; it should form an organic whole. Design Patterns emphasizes that a high-quality naming system stems from a deep understanding of the problem space and demonstrates sufficient abstraction capability. Ideal naming should be brief, symmetrical, and consistent. At a macro level, code is organized into a hierarchical, tree-like structure; at a micro level, layers exhibit analogous bipartite graph relationships.

2.4.1 Consistency and Compatibility

Within a team, the naming style for components must be consistent. When modifying others' code, one should strive to be compatible with and continue the existing naming style, avoiding unnecessary variability:

// Bad Practice: Inconsistent naming style
// student.go
func get(name string) (*Student, error)
func process(student *Student) error

// teacher.go
func fetch(name string) (*Teacher, error)
func handle(teacher *Teacher) error

// Good Practice: Consistent naming style
// student.go
func get(name string) (*Student, error)
func process(student *Student) error

// teacher.go
func get(name string) (*Teacher, error)
func process(teacher *Teacher) error

The use of similar names to denote different concepts significantly increases the maintainer's cognitive burden.

2.4.2 Atomicity and Orthogonality

Functions should aim to be small and focused so their names can fully reflect their single responsibility. Core functions should be as orthogonal as possible, reducing redundancy, and drawing power from combination mechanisms:

// Good Practice: Atomic and orthogonal function design
type StudentManager struct {}

func (m *StudentManager) Create(id, name string, age int) (*Student, error)
func (m *StudentManager) Read(id string) (*Student, error)
func (m *StudentManager) Update(s *Student) error
func (m *StudentManager) Delete(id string) error

In web services, providing CRUD (Create, Read, Update, Delete) operations around a specific entity (like Student) facilitates combinatorial calls from higher-level business logic.

2.4.3 Systematization through Abstraction

Through reasonable module partitioning, the system structure should be naturally clear. This "naturalness" comes from the shared context of the users (including the developer themselves). For example, when designing a system supporting multiple storage backends, the abstraction of the Storage interface allows developers to organize code by referencing common abstractions found in operating systems and databases:

// Good Practice: Systemic abstraction design
type Storage interface {
    Create(uri string) (*File, error)
    Remove(uri string) error
    Open(uri string, mode int) (io.ReadWriter, error)
}

type Task interface {
    Start() error
    Stop() error
    Suspend() error
    Resume() error

    IsRunning() bool
    IsSuspended() bool
}

Using universally familiar concepts (such as the general concepts of "Eagle" and related terminology like "EagleEye," "EagleClaw") to decompose system modules allows for rapid understanding of the system architecture by leveraging shared semantic cognition. This method is particularly effective in large-scale system design.

3. Common Issues and Solutions in Naming Practice

3.1 Balancing Name Length and Information Density

The length of a name does not bear a simple linear relationship to the amount of information it carries. Studies show that excessively long names may not necessarily only provide more useful information but can actually decrease code readability due to increased cognitive burden.

Recommendations:

  • Use domain-specific terminology to compress information.
  • Remove redundant words without introducing ambiguity.
  • Adjust name length based on scope: global variables should be more descriptive, while local variables can be appropriately shorter.

3.2 Linguistic and Cultural Barriers in Naming

In cross-national teams, non-native English speakers may face naming challenges. Research indicates that developers from different cultural backgrounds can have significantly varying naming preferences for the same concept.

Recommendations:

  • Establish a team-shared naming glossary.
  • Use tools to assist in checking the accuracy of English expression.
  • Prioritize the use of standard computer science terminology.

3.3 Naming Stability During Code Evolution

Software systems are constantly evolving, yet naming is typically one of the most stable parts of a system. Inappropriate initial naming can lead to soaring maintenance costs down the line.

Recommendations:

  • Invest sufficient time in naming during the initial design phase.
  • Conduct regular naming refactoring to keep names consistent with system evolution.
  • Establish team standards and processes for name updates.

4. Automation and Tool Support for Naming Standards

4.1 Static Analysis Tools

Various static analysis tools can help identify naming issues:

  • Linters (e.g., ESLint, golint) can check compliance with naming conventions.
  • Complexity analysis tools can identify overly complex functions, which are often accompanied by naming challenges.

4.2 Naming Suggestion Systems

Machine learning-based naming suggestion systems are starting to be applied in software development:

  • Tools like GitHub Copilot can provide naming suggestions based on function implementation.
  • Code completion systems can suggest variable and function names based on context.

4.3 Automated Team Naming Conventions

Establish and automate team-specific naming standards:

  • Customize linter rules to enforce team naming conventions.
  • Introduce naming quality checkpoints during the code review process.
  • Utilize CI/CD pipelines to verify naming compliance.

5. Conclusion

Just as classical writers needed repeated deliberation to craft a masterpiece, naming in software development requires meticulous refinement to extend the code's lifespan and prevent its premature "decay." This study provides a systematic naming methodology for developers by deeply analyzing the core principles of naming—Integrity, Explicitness, Conciseness, and Systemicity.

The research confirms that programmers spend significantly more time reading and understanding code than writing it. If one only pursues one-off coding speed and names haphazardly based on initial reactions, the code is highly likely to face immediate refactoring or obsolescence. Prudent, deliberate naming is essential for enhancing software quality, reducing maintenance costs, and fostering better team collaboration.

Future research directions include: exploring optimal naming practices in domain-specific languages, developing more intelligent naming assistance tools, and quantifying the impact of different naming strategies on code maintenance efficiency. Through these efforts, we aim to further elevate the quality of naming in software engineering practice, ultimately achieving higher quality and more maintainable software systems.