AS.

07 de agosto de 2024

Negative Space

The importance of defining what an application should not do.

Negative space is a design concept that highlights the value of the empty area around an object, instead of focusing exclusively on the object itself. This principle can be applied to various fields, including programming, where what is absent can be just as essential as what is present.

Application in Programming

In programming, the concept of negative space refers to the importance of defining not only what an application should do but also what it should not do. Establishing these clear boundaries offers several benefits:

  1. Improved Bug Detection: By explicitly specifying what the application should not do, it becomes easier to detect and fix undesirable behavior.
  2. Simplified Development: With well-defined limits, the development of new features becomes more straightforward and organized.
  3. Rejection of Inadequate Features: Clear criteria make it possible to reject functionalities that exceed the planned scope of the application.

For example, in the context of microservices, the ideal approach is often not to add a new capability to an existing service (Service A), but rather to transfer it to a more suitable service (Service B) or create a dedicated new service.

Practical Applications

  1. API Design: When designing an API, it’s crucial to define not only the endpoints and their functionalities but also the use cases that will not be supported.
  2. Security: Specifying what is not allowed helps prevent vulnerabilities and attacks.
  3. Maintenance and Scalability: Limiting the scope of features makes maintenance easier and improves scalability.

Code Example

The concept of negative space can be applied to code in many ways. For example, when defining a function that should only accept integers, you can reject decimal values by applying a negative space approach:

function onlyIntegers(number) {
  if (number % 1 !== 0) {
    throw new Error('Only integers are allowed');
  }
  return number;
}

Another approach is to use assertions to ensure that certain conditions are not violated (such as avoiding null values), for example:

function divide(dividend, divisor) {
  console.assert(divisor !== 0, 'Division by zero is not allowed');
  return dividend / divisor;
}

These examples demonstrate the effectiveness of dealing with errors before they occur, rather than handling them afterward. This results in gains in both performance and code readability.


Error Handling in Go: A Practical Negative Space Example

Go embraces the idea of negative space in its unique error handling model. Instead of hiding errors behind exceptions (as in Java or Python), Go requires developers to explicitly check for what should not happen after each operation.

For example:

package main

import (
    "fmt"
    "os"
)

func main() {
    f, err := os.Open("file.txt")
    if err != nil {
        // Explicitly handle what should NOT happen
        fmt.Println("Error:", err)
        return
    }
    defer f.Close()

    fmt.Println("File opened successfully")
}

Advantages of Go’s Approach

  1. Explicit Boundaries: Every function call that can fail returns an error. The programmer must decide how to handle the “negative space” (undesirable cases).
  2. Improved Reliability: By constantly checking for errors, bugs and edge cases are detected closer to their source.
  3. No Hidden Control Flow: Unlike exceptions, which can jump across the stack and obscure logic, Go makes error handling a part of the normal flow of the program.
  4. Encourages Simplicity: Go’s philosophy is that clear, explicit code is easier to maintain and reason about—reinforcing the principle that what the program should not do is just as important as what it does.

This design reflects the negative space principle: by focusing on explicitly handling absence, failure, and invalid states, Go strengthens the reliability and clarity of applications.