Merge pull request #161 from Mikek16/master

Editing changes to chapters 7&8
This commit is contained in:
Amy J. Ko 2023-10-10 10:42:44 -07:00 committed by GitHub
commit 551b7baecf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 4 additions and 4 deletions

View file

@ -4,7 +4,7 @@ This is where *architecture* comes in. Architecture is a way of organizing code,
When encapsulation strategies fail, one can end up with what some affectionately call a "ball of mud" architecture or "spaghetti code". Ball of mud architectures have no apparent organization, which makes it difficult to comprehend how parts of its implementation interact. A more precise concept that can help explain this disorder is *cross-cutting concerns*, which are things like features and functionality that span multiple different components of a system, or even an entire system. There is some evidence that cross-cutting concerns can lead to difficulties in program comprehension and long-term design degradation<walker12>, all of which reduce productivity and increase the risk of defects. As long-lived systems get harder to change, they can take on _technical debt_, which is the degree to which an implementation is out of sync with a team's understanding of what a product is intended to be. Many developers view such debt as emerging from primarily from poor architectural decisions<ernst15>. Over time, this debt can further result in organizational challenges<khadka14>, making change even more difficult.
The preventative solution to this problems is to try to design architecture up front, mitigating the various risks that come from cross-cutting concerns (defects, low modifiability, etc.)<fairbanks10>. A popular method in the 1990's was the [Unified Modeling Language|https://en.wikipedia.org/wiki/Unified_Modeling_Language] (UML), which was a series of notations for expressing the architectural design of a system before implementing it. Recent studies show that UML generally not used and generally not universal<petre13>. While these formal representations have generally not been adopted, informal, natural language architectural specifications are still widely used. For example, [Google engineers write design specifications|https://www.industrialempathy.com/posts/design-docs-at-google/] to sort through ambiguities, consider alternatives, and clarify the volume of work required. A study of developers' perceptions of the value of documentation also reinforced that many forms of documentation, including code comments, style guides, requirements specifications, installation guides, and API references, are viewed as critical, and are only viewed as less valuable because teams do not adequately maintain them<aghajani20>.
The preventative solution to this problems is to try to design architecture up front, mitigating the various risks that come from cross-cutting concerns (defects, low modifiability, etc.)<fairbanks10>. A popular method in the 1990's was the [Unified Modeling Language|https://en.wikipedia.org/wiki/Unified_Modeling_Language] (UML), which was a series of notations for expressing the architectural design of a system before implementing it. Recent studies show that UML was generally not used and generally not universal<petre13>. While these formal representations have generally not been adopted, informal, natural language architectural specifications are still widely used. For example, [Google engineers write design specifications|https://www.industrialempathy.com/posts/design-docs-at-google/] to sort through ambiguities, consider alternatives, and clarify the volume of work required. A study of developers' perceptions of the value of documentation also reinforced that many forms of documentation, including code comments, style guides, requirements specifications, installation guides, and API references, are viewed as critical, and are only viewed as less valuable because teams do not adequately maintain them<aghajani20>.
More recent developers have investigated ideas of *architectural styles*, which are patterns of interactions and information exchange between encapsulated components. Some common architectural styles include:
@ -20,8 +20,8 @@ One fundamental unit of which an architecture is composed is a *component*. This
The second fundamental unit of architecture is *connectors*. Connectors are code that transmit information _between_ components. They're brokers that connect components, but do not necessarily have meaningful behaviors or states of their own. Connectors can be things like function calls, web service API calls, events, requests, and so on. None of these mechanisms store state or functionality themselves; instead, they are the things that tie components functionality and state together.
Even with carefully selected architectures, systems can still be difficult to put together, leading to *architectural mismatch*<garlan95>. When mismatch occurs, connecting two styles can require dramatic amounts of code to connect, imposing significant risk of defects and cost of maintenance. One common example of mismatches occurs with the ubiquitous use of database schemas with client/server web-applications. A single change in a database schema can often result in dramatic changes in an application, as every line of code that uses that part of the scheme either directly or indirectly must be updated<qiu13>. This kind of mismatch occurs because the component that manages data (the database) and the component that renders data (the user interface) is highly "coupled" with the database schema: the user interface needs to know _a lot_ about the data, its meaning, and its structure in order to render it meaningfully.
Even with carefully selected architectures, systems can still be difficult to put together, leading to *architectural mismatch*<garlan95>. When mismatch occurs, connecting two styles can require dramatic amounts of code to connect, imposing significant risk of defects and cost of maintenance. One common example of mismatches occurs with the ubiquitous use of database schemas with client/server web-applications. A single change in a database schema can often result in dramatic changes in an application, as every line of code that uses that part of the scheme either directly or indirectly must be updated<qiu13>. This kind of mismatch occurs because the component that manages data (the database) and the component that renders data (the user interface) are highly "coupled" with the database schema: the user interface needs to know _a lot_ about the data, its meaning, and its structure in order to render it meaningfully.
The most common approach to dealing with both architectural mismatch and the changing of requirements over time is *refactoring*, which means changing the _architecture_ of an implementation without changing its behavior. Refactoring is something most developers do as part of changing a system<murphyhill09,silva16>. Refactoring code to eliminate mismatch and technical debt can simplify change in the future, saving time<ng06> and prevent future defects<kim12>. However, because refactoring remains challenging, the difficulty of changing an architecture is often used as a rationale for rejecting demands for change from users. For example, Google does not allow one to change their Gmail address, which greatly harms people who have changed their name (such as this author when she came out as a trans woman), forcing them to either live with an address that includes their old name, or abandon their Google account, with no ability to transfer documents or settings. The rationale for this has nothing to do with policy and everything to do with the fact that the original architecture of Gmail treats the email address as a stable, unique identifier for an account. Changing this basic assumption throughout Gmail's implementation would be an immense refactoring task.
The most common approach to dealing with both architectural mismatch and the changing of requirements over time is *refactoring*, which means changing the _architecture_ of an implementation without changing its behavior. Refactoring is something most developers do as part of changing a system<murphyhill09,silva16>. Refactoring code to eliminate mismatch and technical debt can simplify change in the future, saving time<ng06> and preventing future defects<kim12>. However, because refactoring remains challenging, the difficulty of changing an architecture is often used as a rationale for rejecting demands for change from users. For example, Google does not allow one to change their Gmail address, which greatly harms people who have changed their name (such as this author when she came out as a trans woman), forcing them to either live with an address that includes their old name, or abandon their Google account, with no ability to transfer documents or settings. The rationale for this has nothing to do with policy and everything to do with the fact that the original architecture of Gmail treats the email address as a stable, unique identifier for an account. Changing this basic assumption throughout Gmail's implementation would be an immense refactoring task.
Research on the actual activity of software architecture is actually somewhat sparse. One of the more recent syntheses of this work is Petre et al.'s book, _Software Design Decoded_<petre16>, which distills many of the practices and skills of software design into a set of succinct ideas. For example, the book states, "_Every design problem has multiple, if not infinite, ways of solving it. Experts strongly prefer simpler solutions over complex ones, for they know that such solutions are easier to understand and change in the future._" And yet, in practice, studies of how projects use APIs often show that developers do the exact opposite, building projects with dependencies on large numbers of sometimes trivial APIs. Some behavior suggests that while software _architects_ like simplicity of implementation, software _developers_ are often choosing whatever is easiest to build, rather than whatever is least risky to maintain over time<abdalkareem17>.

View file

@ -4,7 +4,7 @@ As you've probably noticed by now, this type of process doesn't really scale, ev
The techniques we've discussed so far for avoiding this boil down to _specifying_ what code should do, so everyone can write code according to a plan. We've talked about [requirements specifications|requirements], which are declarations of what software must do from a users' perspective. We've also talked about [architectural specifications|architecture], which are high-level declarations of how code will be organized, encapsulated, and coordinated. At the lowest level are *functional specifications*, which are declarations about the _properties of input and output of functions in a program_.
In their simplest form, a functional specification can be a just some natural language that says what an individual function is supposed to do:
In their simplest form, a functional specification can be just some natural language that says what an individual function is supposed to do:
`javascript
// Return the smaller of the two numbers,