Talks

What's in a Name: From Variables to Domain-Driven Design

What's in a Name: From Variables to Domain-Driven Design

by Karynn Ikeda

In her presentation "What's in a Name: From Variables to Domain-Driven Design" at RailsConf 2024, Karynn Ikeda explores the intricate role of naming in software engineering, emphasizing its importance in understanding and building sign systems. Drawing from her background in semiotics, linguistics, and programming, Karynn illustrates how names function as signs that convey meaning within codebases. The discussion begins with the basic concept of signs, highlighting the distinction between the signifier (the name) and the signified (the associated concept). Key points include:

  • Sign Systems: Software engineers design unique sign systems through variable and class names, which should convey meaning clearly to other developers.
  • Naming Challenges: Naming is a fundamental challenge in computer science as it affects both machine communication and collaboration among programmers. Names must be meaningful to provide context and intent.
  • Examples of Naming: Karynn compares vague names like "X" with more descriptive alternatives, arguing that meaningful names enhance clarity, especially in collaborative environments where context may not always be apparent.
  • Naming Conventions: The use of pluralization, capitalization, and punctuation in Ruby carries specific meanings; these conventions help shape the expectations and interpretations of names in the code.
  • Semantic Relationships: Karynn discusses how names can share similar structures yet signify different concepts, leading to potential confusion. This can be mitigated through thoughtful naming practices that consider context.
  • Domain-Driven Design: The presentation ties back to the principles of domain-driven design, advocating for a shared language between different stakeholders that aligns software names with real-world concepts, facilitating communication and reducing semantic misunderstandings.
  • Real-World Analogies: Karynn uses the metaphor of the assembly line, derived from the meat-packing industry, to explain how terminology evolves in different contexts.

In conclusion, the power of meaningful naming is underscored as a crucial part of software development, allowing for improved collaboration and understanding. Effective naming not only facilitates communication within code but also bridges the gap between technology and the business domain, ultimately leading to more robust and navigable systems.

00:00:10.120 Welcome to the presentation titled "From Variables to Domain-Driven Design."
00:00:12.400 Our speaker today is Karynn Ikeda. Karynn is a lover of language, inspired by ancient Latin and modern German, as well as the language we all use—Ruby.
00:00:17.400 Currently a staff engineer at Bist, Karynn leverages semiotics within a Rails codebase to design systems that are easily readable and self-explanatory. Let’s give a warm welcome to Karynn!
00:00:36.160 Thank you so much! It’s wonderful to be here. This is my first time at RailsConf, so I’m very excited to be joining you as a speaker today.
00:00:39.040 As mentioned, my name is Karynn and I work as a staff engineer at Bist. However, I haven't always been a software engineer. My fascination with language led me to study semiotics, linguistics, and philosophy in college.
00:00:49.280 So what is semiotics? It is the study of signs and their use and interpretation. Initially, I thought semiotics was purely academic until I became a software engineer. One of our core jobs as software engineers is to be builders of sign systems.
00:01:06.640 Natural language is the primary sign system we use daily to communicate the thoughts in our minds, just as I am speaking to you now. As software engineers, we use programming languages, such as Ruby, to articulate the behavior of our technical systems.
00:01:20.720 When I say that we are builders of sign systems, I mean that we are inventing our own language. Every variable and class is a creation of developers, unique to the repositories and applications they occupy.
00:01:32.920 As builders of sign systems, it's vital that we grasp how signs work to design better systems. Now, what is a sign? A sign comprises two parts: the signifier and the signified. To illustrate this relationship, I’d like to share an art piece by René Magritte that exemplifies this concept.
00:01:52.280 In this piece, a pipe extends from the frame, and the message reads, "This is not a pipe." While this may seem obvious, the cursive lines of the word "pipe" constitute the signifier, while the physical pipe jutsting out represents the signified.
00:02:00.239 Together, the cursive word and the physical object form a sign. Upon seeing or hearing the word "pipe," our minds invoke the literal concept of a pipe.
00:02:09.840 In relating this back to language, the letters for "pipe" are not universal signifiers. Here is "pipe" represented in a few different languages, all of which should invoke the physical object to speakers of those languages. We can deduce that signs are invented; each language develops its own symbol or sign for "pipe." This brings me to my first point about names: signs are arbitrary.
00:02:29.680 If names can be anything, what determines if one name is superior to another? Today, my talk will focus on the declared name—the signifier—and its role as a sign within an application.
00:02:41.360 Naming is considered one of the hardest challenges in computer science. Choosing and utilizing a name extends a sign system that we want to be comprehensible to other individuals. We need to understand how a name functions within the system to utilize it effectively.
00:03:03.680 Let's take a look at the humble variable.
00:03:04.840 Variable declaration and assignment originated as mechanisms for the machine to know where to store pieces of data, so they could be read or overwritten later in the program. We, as programmers, communicate with the machine. However, if variables are an abstraction for machine language, why is it essential to have meaningful names in our codebases?
00:03:23.680 Couldn’t we use X to serve the purpose? Indeed, if we were only communicating with the machine, we could stop here. But in today’s collaborative codebases, variable names must communicate intent to fellow programmers.
00:03:35.120 A programming language serves two functions: communicating with the machine and with other programmers. Particularly in an untyped language like Ruby, there are no guarantees about what is being signified.
00:03:49.920 Names act as signs, conveying what is signified to other humans. The human aspect is crucial; the machine doesn't care that you've assigned zero to X, but the human does. The variable X only holds meaning if you understand the context.
00:04:03.440 You can see the variable assignment in the code, and in that context, the relationship between X and what it signifies is clear. But what happens when your code is viewed out of context? This scenario occurs often.
00:04:17.760 For example, consider controller instance variables in Rails. Rails copies controller instance variables to the view initializer, leading programmers to think that these are semantically identical variables. Thus, when we see these variables in the view, we read them out of context and rely on the strength of the variable's name to convey what is signified, since we cannot directly see the variable declaration in the view.
00:04:43.320 Returning to our earlier example, let’s assign a more meaningful name. Is "bit" a better name than "X"? I assert that it is only better because it draws on external context. In English, a bit is defined as a unit of information represented as either zero or one in binary notation.
00:04:57.760 Thus, we are slightly cheating in making this comparison. Names, therefore, possess no inherent meaning; they derive significance from their differences compared to other names.
00:05:09.360 Consider the two names: "bit" and "bits." The addition of an 's' conveys certain assumptions about each. If you guessed that one is a scalar and the other a collection, you would be correct.
00:05:22.920 Pluralization instantly communicates a lot to us in both natural language and in code. Rails utilizes singular and plural throughout its MVC framework to dictate naming conventions.
00:05:34.520 This presents a comfortable framework for us as Rails developers. What about differences where the characters remain the same but capitalization varies?
00:05:40.000 We know that if we use all caps, conventionally this signifies a constant in Ruby rather than a variable. This leads us to certain assumptions about whether or not we expect the value to change.
00:05:50.160 What happens when we introduce punctuation? For instance, adding a question mark often indicates a method in Ruby that might return a Boolean value. Thus, pluralization, capitalization, and punctuation each possess meanings in Ruby distinct from how we use these differences in natural language.
00:06:05.000 We’ve already begun generating a significant amount of meaning based on subtle differences. Let’s return to X. This variable could represent anything, but do you perceive it as different now?
00:06:12.120 Even if we have no prior knowledge of X, can we make assumptions based on our discussions? We can leverage our understanding of coding conventions to aid our comprehension.
00:06:29.360 Another way to consider difference is through the concept of binary opposition, the idea that two concepts cannot coexist simultaneously because they are defined in relation to one another. In binary notation, this is expressed as ones and zeros, or on and off. In Ruby and other languages, we express this through booleans—true and false.
00:06:49.360 We can also define these two constants as exact opposites. For instance, if I change "stop" to "true," "go" would immediately become "false." This provides a prime example of how meaning within a sign system is interdependent.
00:07:01.600 As we continue to evolve sign systems, like a codebase, we must reconcile how the introduction of a new sign or name alters the meaning of existing signs. Here, for example, we’ve introduced "slow," defining it as a false class and a true class.
00:07:17.760 What does "slow" equal in this example? We retain the names—the signifiers—while altering the values that are signified for it to make sense. This offers flexibility, but you must be cautious when considering binary oppositions.
00:07:30.160 English provides us with tools to create similar types of opposition through prefixes and suffixes, such as "inaccessible" versus "accessible" or "fearless" and "fear." Thus, signs are not static; their meanings will evolve as other names are added or removed from a system.
00:07:48.120 What happens if we eliminate differences? Fortunately, in the global scope, a variable or method name cannot be reused without overwriting its definition. However, names can still be reused when considering different scopes, which leads us to classes.
00:08:07.480 Returning to our art piece, it references an earlier work by René Magritte where the message beneath the pipe translates to "This is not a pipe." Here, we see that the name "pipe" signifies both a plumbing pipe and a smoking pipe.
00:08:23.760 Within the context of each art piece, this is internally consistent. However, when comparing the two pieces, the semantic meaning becomes less clear. Classes act similarly in our code; when attributes and methods are private, we only need to be concerned with the semantic relationships within the class.
00:08:39.120 Imagine we find a wheel for steering and tires for motion. When attributes and methods are public, we need to consider the semantic interactions between different objects.
00:08:55.680 In this instance, we also have a unicycle, which has a wheel. Is the car's wheel the same as the unicycle's wheel? Unlike art, we cannot easily see inside the frame to determine if "wheel" signifies the same concept; we must look at the unicycle class to find out.
00:09:10.880 Here, the unicycle wheel is more similar to the car's tires, while the handle correlates more closely with the car's wheel. When the same signifiers are utilized together but signify different concepts, it can create cognitive dissonance, complicating our understanding of the code.
00:09:29.760 Names of signifiers can be identical but signify different concepts, resulting in potential confusion. However, it is important to note that difference is not inherently negative; as long as the terms signify the same concept, everything is okay.
00:09:45.880 We can leverage this through duck typing; if "go" conveys a similar concept, even if implemented slightly differently across classes, we can create cognitive similarity.
00:10:02.560 Reflecting on the artwork, consider how both pieces symbolize the act of signification within their respective frames, becoming signs themselves while drawing connections to the broader realm of art.
00:10:20.920 Signs are not one-dimensional; they can encompass other signs. A further example of this phenomenon is found in compound words within English—such as "automobile," which when literally interpreted means "self-moving." This vehicle represents something distinct from its components.
00:10:36.760 Classes encapsulate variable and method names, acting as their own unique signs. Similarly, the introduction of new named classes will alter the semantic relationships of existing names, affecting both higher and lower-level signs.
00:10:52.079 Imagine we are Victorian developers considering a semantic model for a Victorian vehicle with wheels, a chassis, and horses. Upon introducing the "horseless carriage," how does this transform our sign relationships?
00:11:10.720 Initially, it may seem little has changed. We can accommodate this new concept easily, as it shares many attributes with a carriage. We could even devise a method to differentiate between a carriage and a horseless carriage based on whether it has horses before considering the engine.
00:11:25.920 However, if we want to introduce buses, cars, or other types of moving objects, we need to rethink our decision to use the term "horseless carriage." In fact, this term was merely another name for the automobile.
00:11:40.560 When we transitioned to "automobile," we altered not just the name of the horseless carriage but the very way we conceptualized "horse"—shifting its definition towards "engine" and transforming "horsepower" into an abstract unit of power.
00:11:53.120 Consider how many "horseless carriages" you may still carry in your codebase. We have discussed various strategies for structuring our sign systems through classes. However, as codebases expand, we may need to consider if classes are sufficient.
00:12:06.680 We’ve witnessed how microservices break down code into manageable components, allowing individual teams to own specific portions. The computer doesn’t care how we organize our files as long as they’re accessible—this is primarily a human problem associated with scaling our code as engineering organizations grow.
00:12:20.439 This necessitates an organizational structure to understand how different pieces relate, thereby enabling task breakdowns and ownership assignment. Another principle gaining traction in the Ruby on Rails community is the concept of the modular monolith.
00:12:34.880 Rails provides organizing principles based on the Model-View-Controller design pattern, functioning as an organizational approach. While Rails is not particularly opinionated, it introduces the idea of namespaces, allowing for organized frameworks.
00:12:49.440 However, within this MVC model, we lack a holistic view of all that pertains to a given namespace, splitting the information across different hierarchies in our directory structure.
00:13:06.319 This segregation leads to multiple frames. Fortunately, numerous tools now assist in organizing our code. One such tool is Pax modularization, where each pack represents a modularized domain package.
00:13:20.720 We can describe these packages using tools like Packwork to define dependencies, ultimately helping us create a hierarchy that makes sense when we want to aggregate classes and files.
00:13:38.960 Instead of having your namespace buried amongst each model's views and controllers, using packed Rails helps us position our packages closer to the root so we can view all of our code in the context of that MVC model.
00:13:51.360 However, to do this effectively, we need to consider what constitutes a good package. Robert Martin's guidelines promote strong functional cohesion, emphasizing that we should group together classes likely to be reused in tandem.
00:14:06.760 Conversely, we should avoid grouping classes that aren't functionally related. This principle makes logical sense, and as one of the earliest Paxwork adopters, Shopify has conducted retrospectives on this approach.
00:14:20.799 Developers tend to cluster code into packages based on semantic clues that might have little relation to how the code actually runs. This approach seems to contradict Martin’s emphasis on grouping code by function.
00:14:34.880 It is tempting to cluster similar-sounding classes together for semantic clarity—an instinct we’ve established nurtures difference. By violating this notion of difference, we risk creating cognitive dissonance.
00:14:47.760 If I divide two files with manufacturing-related names, I must address the question of what "manufacturing" truly represents. This leads us to the interplay of functional coupling and semantic coupling, which may not always align.
00:15:00.000 We often make tough decisions prioritizing one aspect over the other. For Shopify, focusing on functional cohesion means setting aside semantic coupling for a future consideration.
00:15:12.199 As software engineers, we may notice these tensions, yet we often overlook semantic debt as a subset of technical debt. Semantic issues don't typically emerge from coding shortcuts, but rather from the system's evolution over time.
00:15:26.339 As we add new names, classes, and variables, we might not recognize that the sign system is evolving. It’s crucial to understand that renaming can be a significant investment, and this outline isn't a suggestion to rename every file or class.
00:15:40.000 Instead, it sheds light on how these semantic contradictions arise in our codebases. Treating it as any other form of technical debt requires prioritization, as there are costs to consider.
00:15:55.760 These costs include longer onboarding times for new team members as they navigate semantic contradictions, potentially introducing bugs due to misinterpretations of how specific code operates.
00:16:10.920 Is there a better approach? Most of us manage systems mirroring the real world. Therefore, modeling our code on names used in the real world can help mitigate semantic issues since business domains generally change at a slower pace.
00:16:25.720 This principle represents the core of domain-driven design, which aligns the design of our software with our comprehension of the real-world business domain.
00:16:40.000 Domain-driven design starts with a focus on language, rooted in semantics, and observes how that language is utilized outside of code before embedding these names back into the software system.
00:16:55.040 The concept of ubiquitous language implies that by modeling our signs after the real world, we can reduce the mental burden required for semantic understanding since we will also inherit natural associations.
00:17:08.080 This shared language can facilitate clearer communication between different stakeholders, fostering discussions regarding how our systems should evolve.
00:17:20.560 This is particularly relevant for established business domains, however, our innovation-driven field may produce coding that creates entirely new domains.
00:17:38.440 Consider social media—a domain that originated with no direct real-world counterpart. In this case, how should we choose our names? Reflect on the power of metaphor.
00:17:58.560 We find ourselves in Detroit today, the birthplace of the first mass-produced automobile and a fascinating concept called the assembly line. Yet, it's crucial to note that Henry Ford didn't invent the assembly line; he appropriated the idea from the meat-packing industry.
00:18:12.640 Ford’s innovation lay in reframing how the assembly line applied to automobile manufacturing rather than merely inventing the concept.
00:18:27.420 In meat packing, butchers cut parts from the animal as it moved along the line—an operation that Ford imagined in reverse. Instead of removing parts, they would add them.
00:18:41.440 This is not a perfect analogy, but it highlights the significant similarities that can be leveraged when designing systems based on language from other industries.
00:18:56.000 Consider the completed car, which occupies a similar conceptual space as the animal's body. We see here our metaphor stretches across to connect our horses and combustible engines.
00:19:09.000 As we conclude, remember that names establish a framework for defining problems within our systems. We’re not just presenting our fellow developers with a glossary of terms but with a comprehensive sign system.
00:19:24.000 Understanding a name's role within this system allows us to collaborate effectively and construct superior systems together.
00:19:40.000 I'd like to leave you with one final thought. When the creators of Unix sought to express how to connect the output of one function to the input of another, they aptly turned to the metaphor of pipes.
00:19:56.960 This metaphor simplifies our understanding of what the symbol indicates and how it should function. It serves as a classic example of a lasting name that continues to resonate today.
00:20:11.280 Thank you!