Removed spaces in front of citations.

This commit is contained in:
Amy J. Ko 2020-09-11 10:37:17 -07:00
parent 6bfb475a4d
commit 08a6e558ff
13 changed files with 83 additions and 83 deletions

View file

@ -2,26 +2,26 @@ Once you have a sense of what your design must do (in the form of requirements o
This is where *architecture* comes in. Architecture is a way of organizing code, just like building architecture is a way of organizing space. The idea of software architecture has at its foundation a principle of *information hiding*: the less a part of a program knows about other parts of a program, the easier it is to change. The most popular information hiding strategy is *encapsulation*: this is the idea of designing self-contained abstractions with well-defined interfaces that separate different concerns in a program. Programming languages offer encapsulation support through things like *functions* and *classes*, which encapsulate data and functionality together. Another programming language encapsulation method is *scoping*, which hides variables and other names from other parts of program outside a scope. All of these strategies attempt to encourage developers to maximize information hiding and separation of concerns. If you get your encapsulation right, you should be able to easily make changes to a program's behavior without having to change _everything_ about it's implementation. This is where *architecture* comes in. Architecture is a way of organizing code, just like building architecture is a way of organizing space. The idea of software architecture has at its foundation a principle of *information hiding*: the less a part of a program knows about other parts of a program, the easier it is to change. The most popular information hiding strategy is *encapsulation*: this is the idea of designing self-contained abstractions with well-defined interfaces that separate different concerns in a program. Programming languages offer encapsulation support through things like *functions* and *classes*, which encapsulate data and functionality together. Another programming language encapsulation method is *scoping*, which hides variables and other names from other parts of program outside a scope. All of these strategies attempt to encourage developers to maximize information hiding and separation of concerns. If you get your encapsulation right, you should be able to easily make changes to a program's behavior without having to change _everything_ about it's implementation.
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. 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 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: 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:
* *Client/server*, in which data is transacted in response to requests. This is the basis of the Internet and cloud computing <cito15>. * *Client/server*, in which data is transacted in response to requests. This is the basis of the Internet and cloud computing<cito15>.
* *Pipe and filter*, in which data is passed from component to component, and transformed and filtered along the way. Command lines, compilers, and machine learned programs are examples of pipe and filter architectures. * *Pipe and filter*, in which data is passed from component to component, and transformed and filtered along the way. Command lines, compilers, and machine learned programs are examples of pipe and filter architectures.
* *Model-view-controller (MVC)*, in which data is separated from views of the data and from manipulations of data. Nearly all user interface toolkits use MVC, including popular modern frameworks such as React. * *Model-view-controller (MVC)*, in which data is separated from views of the data and from manipulations of data. Nearly all user interface toolkits use MVC, including popular modern frameworks such as React.
* *Peer to peer (P2P)*, in which components transact data through a distributed standard interface. Examples include Bitcoin, Spotify, and Gnutella._ * *Peer to peer (P2P)*, in which components transact data through a distributed standard interface. Examples include Bitcoin, Spotify, and Gnutella._
* *Event-driven*, in which some components "broadcast" events and others "subscribe" to notifications of these events. Examples include most model-view-controller-based user interface frameworks, which have models broadest change events to views, so they may update themselves to render new model state. * *Event-driven*, in which some components "broadcast" events and others "subscribe" to notifications of these events. Examples include most model-view-controller-based user interface frameworks, which have models broadest change events to views, so they may update themselves to render new model state.
Architectural styles come in all shapes and sizes. Some are smaller design patterns of information sharing <beck96>, whereas others are ubiquitous but specialized patterns such as the architectures required to support undo and cancel in user interfaces <bass03>. Architectural styles come in all shapes and sizes. Some are smaller design patterns of information sharing<beck96>, whereas others are ubiquitous but specialized patterns such as the architectures required to support undo and cancel in user interfaces<bass03>.
One fundamental unit of which an architecture is composed is a *component*. This is basically a word that refers to any abstraction&mdash;any code, really&mdash;that attempts to _encapsulate_ some well defined functionality or behavior separate from other functionality and behavior. For example, consider the Java class _Math_: it encapsulates a wide range of related mathematical functions. This class has an interface that decide how it can communicate with other components (sending arguments to a math function and getting a return value). Components can be more than classes though: they might be a data structure, a set of functions, a library, an API, or even something like a web service. All of these are abstractions that encapsulate interrelated computation and state for some well-define purpose. One fundamental unit of which an architecture is composed is a *component*. This is basically a word that refers to any abstraction&mdash;any code, really&mdash;that attempts to _encapsulate_ some well defined functionality or behavior separate from other functionality and behavior. For example, consider the Java class _Math_: it encapsulates a wide range of related mathematical functions. This class has an interface that decide how it can communicate with other components (sending arguments to a math function and getting a return value). Components can be more than classes though: they might be a data structure, a set of functions, a library, an API, or even something like a web service. All of these are abstractions that encapsulate interrelated computation and state for some well-define purpose.
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. 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) 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.
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 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.
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>. 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

@ -1,17 +1,17 @@
Because software engineering often times distributes work across multiple people, a fundamental challenge in software engineering is ensuring that everyone on a team has the same understanding of what is being built and why. In the seminal book _The Mythical Man Month_, Fred Brooks argued that good software needs to have *conceptual integrity*, both in how it is designed, but also how it is implemented <brooks95>. This is the idea that whatever vision of what is being built must stay intact, even as the building of it gets distributed to multiple people. When multiple people are responsible for implementing a single coherent idea, how can they ensure they all build the same idea? Because software engineering often times distributes work across multiple people, a fundamental challenge in software engineering is ensuring that everyone on a team has the same understanding of what is being built and why. In the seminal book _The Mythical Man Month_, Fred Brooks argued that good software needs to have *conceptual integrity*, both in how it is designed, but also how it is implemented<brooks95>. This is the idea that whatever vision of what is being built must stay intact, even as the building of it gets distributed to multiple people. When multiple people are responsible for implementing a single coherent idea, how can they ensure they all build the same idea?
The solution is effective communication. As [some events|https://www.nytimes.com/2017/08/12/upshot/techs-damaging-myth-of-the-loner-genius-nerd.html] in industry have shown, communication requires empathy and teamwork. When communication is poor, teams become disconnected and produce software defects <bettenburg13>. Therefore, achieving effective communication practices is paramount. The solution is effective communication. As [some events|https://www.nytimes.com/2017/08/12/upshot/techs-damaging-myth-of-the-loner-genius-nerd.html] in industry have shown, communication requires empathy and teamwork. When communication is poor, teams become disconnected and produce software defects<bettenburg13>. Therefore, achieving effective communication practices is paramount.
It turns out, however, that communication plays such a powerful role in software projects that it even shapes how projects unfold. Perhaps the most notable theory about the effect of communication is Conway's Law <conway68>. This theory argues that any designed system--software included--will reflect the communication structures involved in producing it. For example, think back to any course project where you divided the work into chunks and tried to combine them together into a final report at the end. The report and its structure probably mirrored the fact that several distinct people worked on each section of the report, rather than sounding like a single coherent voice. The same things happen in software: if the team writing error messages for a website isn't talking to the team presenting them, you're probably going to get a lot of error messages that aren't so clear, may not fit on screen, and may not be phrased using the terminology of the rest of the site. On the other hand, if those two teams meet regularly to design the error mesages together, communicating their shared knowledge, they might produce a seamless, coherent experience. Not only does software follow this law when a project is created, they also follow this law as projects evolve over time <zhou11>. It turns out, however, that communication plays such a powerful role in software projects that it even shapes how projects unfold. Perhaps the most notable theory about the effect of communication is Conway's Law<conway68>. This theory argues that any designed system--software included--will reflect the communication structures involved in producing it. For example, think back to any course project where you divided the work into chunks and tried to combine them together into a final report at the end. The report and its structure probably mirrored the fact that several distinct people worked on each section of the report, rather than sounding like a single coherent voice. The same things happen in software: if the team writing error messages for a website isn't talking to the team presenting them, you're probably going to get a lot of error messages that aren't so clear, may not fit on screen, and may not be phrased using the terminology of the rest of the site. On the other hand, if those two teams meet regularly to design the error mesages together, communicating their shared knowledge, they might produce a seamless, coherent experience. Not only does software follow this law when a project is created, they also follow this law as projects evolve over time<zhou11>.
Because communication is so central, software engineers are constantly seeking information to further their work, going to their coworkers' desks, emailing them, chatting via messaging platforms, and even using social media <ko07>. Some of the information that developers are seeking is easier to find than others. For example, in the study I just cited, it was pretty trivial to find information about how wrote a line of code or whether a build was done, but when the information they needed resided in someone else's head (e.g., _why_ a particular line of code was written), it was slow or often impossible to retrieve it. Sometimes it's not even possible to find out who has the information. Researchers have investigated tools for trying to quantify expertise by automatically analyzing the code that developers have written, building platforms to help developers search for other developers who might know what they need to know <mockus02,begel10>. Because communication is so central, software engineers are constantly seeking information to further their work, going to their coworkers' desks, emailing them, chatting via messaging platforms, and even using social media<ko07>. Some of the information that developers are seeking is easier to find than others. For example, in the study I just cited, it was pretty trivial to find information about how wrote a line of code or whether a build was done, but when the information they needed resided in someone else's head (e.g., _why_ a particular line of code was written), it was slow or often impossible to retrieve it. Sometimes it's not even possible to find out who has the information. Researchers have investigated tools for trying to quantify expertise by automatically analyzing the code that developers have written, building platforms to help developers search for other developers who might know what they need to know<mockus02,begel10>.
Communication is not always effective. In fact, there are many kinds of communication that are highly problematic in software engineering teams. For example, Perlow <perlow99> conducted an [ethnography|https://en.wikipedia.org/wiki/Ethnography] of one team and found a highly dysfunctional use of interruptions in which the most expert members of a team were constantly interrupted to &ldquo;fight fires&rdquo; (immediately address critical problems) in other parts of the organization, and then the organization rewarded them for their heroics. This not only made the most expert engineers less productive, but it also disincentivized the rest of the organization to find effective ways of _preventing_ the disasters from occurring in the first place. Not all interruptions are bad, and they can increase productivity, but they do increase stress <mark08>. Communication is not always effective. In fact, there are many kinds of communication that are highly problematic in software engineering teams. For example, Perlow<perlow99> conducted an [ethnography|https://en.wikipedia.org/wiki/Ethnography] of one team and found a highly dysfunctional use of interruptions in which the most expert members of a team were constantly interrupted to &ldquo;fight fires&rdquo; (immediately address critical problems) in other parts of the organization, and then the organization rewarded them for their heroics. This not only made the most expert engineers less productive, but it also disincentivized the rest of the organization to find effective ways of _preventing_ the disasters from occurring in the first place. Not all interruptions are bad, and they can increase productivity, but they do increase stress<mark08>.
Communication isn't just about transmitting information; it's also about relationships and identity. For example, the dominant culture of many software engineering work environments--and even the _perceived_ culture--is one that can deter many people from even pursuing careers in computer science. Modern work environments are still dominated by men, who speak loudly, out of turn, and disrespectfully, with sometimes even [sexual harassment|https://www.susanjfowler.com/blog/2017/2/19/reflecting-on-one-very-strange-year-at-uber] <wang16>. Computer science as a discipline, and the software industry that it shapes, has only just begun to consider the urgent need for _cultural competence_ (the ability for individuals and organizations to work effectively when their employee's thoughts, communications, actions, customs, beliefs, values, religions, and social groups vary) <washington20>. Similarly, software developers often have to work with people in other domains such as artists, content developers, data scientists, design researchers, designers, electrical engineers, mechanical engineers, product planners, program managers, and service engineers. One study found that developers' cross-disciplinary collaborations with people in these other domains required open-mindedness about the input of others, proactively informing everyone about code-related constraints, and ultimately seeing the broader picture of how pieces from different disciplines fit together; when developers didn't do these things, collaborations failed, and therefore projects failed <li17>. These are not the conditions for trusting, effective communication. Communication isn't just about transmitting information; it's also about relationships and identity. For example, the dominant culture of many software engineering work environments--and even the _perceived_ culture--is one that can deter many people from even pursuing careers in computer science. Modern work environments are still dominated by men, who speak loudly, out of turn, and disrespectfully, with sometimes even [sexual harassment|https://www.susanjfowler.com/blog/2017/2/19/reflecting-on-one-very-strange-year-at-uber]<wang16>. Computer science as a discipline, and the software industry that it shapes, has only just begun to consider the urgent need for _cultural competence_ (the ability for individuals and organizations to work effectively when their employee's thoughts, communications, actions, customs, beliefs, values, religions, and social groups vary)<washington20>. Similarly, software developers often have to work with people in other domains such as artists, content developers, data scientists, design researchers, designers, electrical engineers, mechanical engineers, product planners, program managers, and service engineers. One study found that developers' cross-disciplinary collaborations with people in these other domains required open-mindedness about the input of others, proactively informing everyone about code-related constraints, and ultimately seeing the broader picture of how pieces from different disciplines fit together; when developers didn't do these things, collaborations failed, and therefore projects failed<li17>. These are not the conditions for trusting, effective communication.
When communication is effective, it still takes time. One of the key strategies for reducing the amount of communication necessary is _knowledge sharing_ tools, which broadly refers to any information system that stores facts that developers would normally have to retrieve from a person. By storing them in a database and making them easy to search, teams can avoid interruptions. The most common knowledge sharing tools in software teams are issue trackers, which are often at the center of communication not only between developers, but also with every other part of a software organization <bertram10>. Community portals, such as GitHub pages or Slack teams, can also be effective ways of sharing documents and archiving decisions <treude11>. Perhaps the most popular knowledge sharing tool in software engineering today is [Stack Overflow|https://stackoverflow.com] <atwood16>, which archives facts about programming language and API usage. Such sites, while they can be great resources, have the same problems as many media, such as gender bias that prevent contributions from women from being rewarded as highly as contributions from men <may19>. When communication is effective, it still takes time. One of the key strategies for reducing the amount of communication necessary is _knowledge sharing_ tools, which broadly refers to any information system that stores facts that developers would normally have to retrieve from a person. By storing them in a database and making them easy to search, teams can avoid interruptions. The most common knowledge sharing tools in software teams are issue trackers, which are often at the center of communication not only between developers, but also with every other part of a software organization<bertram10>. Community portals, such as GitHub pages or Slack teams, can also be effective ways of sharing documents and archiving decisions<treude11>. Perhaps the most popular knowledge sharing tool in software engineering today is [Stack Overflow|https://stackoverflow.com]<atwood16>, which archives facts about programming language and API usage. Such sites, while they can be great resources, have the same problems as many media, such as gender bias that prevent contributions from women from being rewarded as highly as contributions from men<may19>.
Because all of this knowledge is so critical to progress, when developers leave an organization and haven't archived their knowledge somewhere, it can be quite disruptive to progress. Organizations often have single points of failure, in which a single developer may be critical to a team's ability to maintain and enhance a software product <rigby16>. When newcomers join a team and lack the right knowledge, they introduce defects <foucault15>. Some companies try to mitigate this by rotating developers between projects, &ldquo;cross-training&rdquo; them to ensure that the necessary knowledge to maintain a project is distributed across multiple engineers. Because all of this knowledge is so critical to progress, when developers leave an organization and haven't archived their knowledge somewhere, it can be quite disruptive to progress. Organizations often have single points of failure, in which a single developer may be critical to a team's ability to maintain and enhance a software product<rigby16>. When newcomers join a team and lack the right knowledge, they introduce defects<foucault15>. Some companies try to mitigate this by rotating developers between projects, &ldquo;cross-training&rdquo; them to ensure that the necessary knowledge to maintain a project is distributed across multiple engineers.
What does all of this mean for you as an individual developer? To put it simply, don't underestimate the importance of talking. Know who you need to talk to, talk to them frequently, and to the extent that you can, write down what you know both to lessen the demand for talking and mitigate the risk of you not being available, but also to make your knowledge more precise and accessible in the future. It often takes decades for engineers to excel at communication. The very fact that you know why communication is important gives you an critical head start. What does all of this mean for you as an individual developer? To put it simply, don't underestimate the importance of talking. Know who you need to talk to, talk to them frequently, and to the extent that you can, write down what you know both to lessen the demand for talking and mitigate the risk of you not being available, but also to make your knowledge more precise and accessible in the future. It often takes decades for engineers to excel at communication. The very fact that you know why communication is important gives you an critical head start.

View file

@ -1,8 +1,8 @@
Despite all of the activities that we've talked about so far&mdash;communicating, coordinating, planning, designing, architecting&mdash;really, most of a software engineers time is spent reading code <maalej14>. Sometimes this is their own code, which makes this reading easier. Most of the time, it is someone else's code, whether it's a teammate's, or part of a library or API you're using. We call this reading *program comprehension*. Despite all of the activities that we've talked about so far&mdash;communicating, coordinating, planning, designing, architecting&mdash;really, most of a software engineers time is spent reading code<maalej14>. Sometimes this is their own code, which makes this reading easier. Most of the time, it is someone else's code, whether it's a teammate's, or part of a library or API you're using. We call this reading *program comprehension*.
Being good at program comprehension is a critical skill. You need to be able to read a function and know what it will do with its inputs; you need to be able to read a class and understand its state and functionality; you also need to be able to comprehend a whole implementation, understanding its architecture. Without these skills, you can't test well, you can't debug well, and you can't fix or enhance the systems you're building or maintaining. In fact, studies of software engineers' first year at their first job show that a significant majority of their time is spent trying to simply comprehend the architecture of the system they are building or maintaining and understanding the processes that are being followed to modify and enhance them <dagenais10>. Being good at program comprehension is a critical skill. You need to be able to read a function and know what it will do with its inputs; you need to be able to read a class and understand its state and functionality; you also need to be able to comprehend a whole implementation, understanding its architecture. Without these skills, you can't test well, you can't debug well, and you can't fix or enhance the systems you're building or maintaining. In fact, studies of software engineers' first year at their first job show that a significant majority of their time is spent trying to simply comprehend the architecture of the system they are building or maintaining and understanding the processes that are being followed to modify and enhance them<dagenais10>.
What's going on when developers comprehend code? Usually, developers are trying to answer questions about code that help them build larger models of how a program works. Because program comprehension is hard, they avoid it when they can, relying on explanations from other developers rather than trying to build precise models of how a program works on their own <roehm12>. When they do try to comprehend code, developers are usually trying to answer questions. Several studies have many general questions that developers must be able to answer in order to understand programs <sillito06,latoza10>. Here are nearly forty common questions that developers ask: What's going on when developers comprehend code? Usually, developers are trying to answer questions about code that help them build larger models of how a program works. Because program comprehension is hard, they avoid it when they can, relying on explanations from other developers rather than trying to build precise models of how a program works on their own<roehm12>. When they do try to comprehend code, developers are usually trying to answer questions. Several studies have many general questions that developers must be able to answer in order to understand programs<sillito06,latoza10>. Here are nearly forty common questions that developers ask:
1. Which type represents this domain concept or this UI element or action? 1. Which type represents this domain concept or this UI element or action?
2. Where in the code is the text in this error message or UI element? 2. Where in the code is the text in this error message or UI element?
@ -43,15 +43,15 @@ What's going on when developers comprehend code? Usually, developers are trying
If you think about the diversity of questions in this list, you can see why program comprehension requires expertise. You not only need to understand programming languages quite well, but you also need to have strategies for answering all of the questions above (and more) quickly, effectively, and accurately. If you think about the diversity of questions in this list, you can see why program comprehension requires expertise. You not only need to understand programming languages quite well, but you also need to have strategies for answering all of the questions above (and more) quickly, effectively, and accurately.
So how do developers go about answering these questions? Studies comparing experts and novices show that experts use prior knowledge that they have about architecture, design patterns, and the problem domain a program is built for to know what questions to ask and how to answer them, whereas novices use surface features of code, which leads them to spend considerable time reading code that is irrelevant to a question <vonmayrhauser94,latoza07>. Reading and comprehending source code is fundamentally different from those of reading and comprehending natural language <binkley13>; what experts are doing is ultimately reasoning about *dependencies* between code <weiser81>. Dependencies include things like *data dependencies* (where a variable is used to compute something, what modifies a data structure, how data flows through a program, etc.) and *control dependencies* (which components call which functions, which events can trigger a function to be called, how a function is reached, etc.). All of the questions above fundamentally get at different types of data and control dependencies. In fact, theories of how developers navigate code by following these dependencies are highly predictive of what information a developer will seek next <fleming13>, suggesting that expert behavior is highly procedural. This work, and work explicitly investigating the role of identifier names <lawrie06>, finds that names are actually critical to facilitating higher level comprehension of program behavior. So how do developers go about answering these questions? Studies comparing experts and novices show that experts use prior knowledge that they have about architecture, design patterns, and the problem domain a program is built for to know what questions to ask and how to answer them, whereas novices use surface features of code, which leads them to spend considerable time reading code that is irrelevant to a question<vonmayrhauser94,latoza07>. Reading and comprehending source code is fundamentally different from those of reading and comprehending natural language<binkley13>; what experts are doing is ultimately reasoning about *dependencies* between code<weiser81>. Dependencies include things like *data dependencies* (where a variable is used to compute something, what modifies a data structure, how data flows through a program, etc.) and *control dependencies* (which components call which functions, which events can trigger a function to be called, how a function is reached, etc.). All of the questions above fundamentally get at different types of data and control dependencies. In fact, theories of how developers navigate code by following these dependencies are highly predictive of what information a developer will seek next<fleming13>, suggesting that expert behavior is highly procedural. This work, and work explicitly investigating the role of identifier names<lawrie06>, finds that names are actually critical to facilitating higher level comprehension of program behavior.
Of course, program comprehension is not an inherently individual process either. Expert developers are resourceful, and frequently ask others for explanations of program behavior. Some of this might happen between coworkers, where someone seeking insight asks other engineers for summaries of program behavior, to accelerate their learning <ko07>. Others might rely on public forums, such as Stack Overflow, for explanations of API behavior <mamykina11>. These social help seeking strategies are strongly mediated by a developers' willingness to express that they need help to more expert teammates. Some research, for example, has found that junior developers are reluctant to ask for help out of fear of looking incompetent, even when everyone on a team is willing to offer help and their manager prefers that the developer prioritize productivity over fear of stigma <begel08>. And then, of course, learning is just hard. For example, one study investigated the challenges that developers face in learning new programming languages, finding that unlearning old habits, shifting to new language paradigms, learning new terminology, and adjusting to new tools all required materials that could bridge from their prior knowledge to the new language, but few such materials existed <shrestha20>. These findings suggest the critical importance of teams ensuring that newcomers view them as psychologically safe places, where vulnerable actions like expressing a need for help will not be punished, ridiculed, or shamed, but rather validated, celebrated, and encouraged. Of course, program comprehension is not an inherently individual process either. Expert developers are resourceful, and frequently ask others for explanations of program behavior. Some of this might happen between coworkers, where someone seeking insight asks other engineers for summaries of program behavior, to accelerate their learning<ko07>. Others might rely on public forums, such as Stack Overflow, for explanations of API behavior<mamykina11>. These social help seeking strategies are strongly mediated by a developers' willingness to express that they need help to more expert teammates. Some research, for example, has found that junior developers are reluctant to ask for help out of fear of looking incompetent, even when everyone on a team is willing to offer help and their manager prefers that the developer prioritize productivity over fear of stigma<begel08>. And then, of course, learning is just hard. For example, one study investigated the challenges that developers face in learning new programming languages, finding that unlearning old habits, shifting to new language paradigms, learning new terminology, and adjusting to new tools all required materials that could bridge from their prior knowledge to the new language, but few such materials existed<shrestha20>. These findings suggest the critical importance of teams ensuring that newcomers view them as psychologically safe places, where vulnerable actions like expressing a need for help will not be punished, ridiculed, or shamed, but rather validated, celebrated, and encouraged.
While much of program comprehension is individual and social skill, some aspects of program comprehension are determined by the design of programming languages. For example, some programming languages result in programs that are more comprehensible. One framework called the _Cognitive Dimensions of Notations_ <green89> lays out some of the tradeoffs in programming language design that result in these differences in comprehensibility. For example, one of the dimensions in the framework is *consistency*, which refers to how much of a notation can be _guessed_ based on an initial understanding of a language. JavaScript has low consistency because of operators like `==`, which behave differently depending on what the type of the left and right operands are. Knowing the behavior for Booleans doesn't tell you the behavior for a Boolean being compared to an integer. In contrast, Java is a high consistency language: `==` is only ever valid when both operands are of the same type. While much of program comprehension is individual and social skill, some aspects of program comprehension are determined by the design of programming languages. For example, some programming languages result in programs that are more comprehensible. One framework called the _Cognitive Dimensions of Notations_<green89> lays out some of the tradeoffs in programming language design that result in these differences in comprehensibility. For example, one of the dimensions in the framework is *consistency*, which refers to how much of a notation can be _guessed_ based on an initial understanding of a language. JavaScript has low consistency because of operators like `==`, which behave differently depending on what the type of the left and right operands are. Knowing the behavior for Booleans doesn't tell you the behavior for a Boolean being compared to an integer. In contrast, Java is a high consistency language: `==` is only ever valid when both operands are of the same type.
These differences in notation can have some impact. Encapsulation through data structures leads to better comprehension that monolithic or purely functional languages <woodfield81,bhattacharya11>. Declarative programming paradigms (like CSS or HTML) have greater comprehensibility than imperative languages <salvaneschi14>. Statically typed languages like Java (which require developers to declare the data type of all variables) result in fewer defects <ray14>, better comprehensibility because of the ability to construct better documentation <endrikat14>, and result in easier debugging <hanenberg13>. In fact, studies of more dynamic languages like JavaScript and Smalltalk <callaú13> show that the dynamic features of these languages aren't really used all that much anyway. Despite all of these measurable differences, the impact of notation seems to be modest in practice <ray14>. All of this evidence suggests that that the more you tell a compiler about what your code means (by declaring types, writing functional specifications, etc.), the more it helps the other developers know what it means too, but that this doesn't translate into huge differences in defects. These differences in notation can have some impact. Encapsulation through data structures leads to better comprehension that monolithic or purely functional languages<woodfield81,bhattacharya11>. Declarative programming paradigms (like CSS or HTML) have greater comprehensibility than imperative languages<salvaneschi14>. Statically typed languages like Java (which require developers to declare the data type of all variables) result in fewer defects<ray14>, better comprehensibility because of the ability to construct better documentation<endrikat14>, and result in easier debugging<hanenberg13>. In fact, studies of more dynamic languages like JavaScript and Smalltalk<callaú13> show that the dynamic features of these languages aren't really used all that much anyway. Despite all of these measurable differences, the impact of notation seems to be modest in practice<ray14>. All of this evidence suggests that that the more you tell a compiler about what your code means (by declaring types, writing functional specifications, etc.), the more it helps the other developers know what it means too, but that this doesn't translate into huge differences in defects.
Code editors, development environments, and program comprehension tools can also be helpful. Early evidence showed that simple features like syntax highlighting and careful typographic choices can improve the speed of program comprehension <baecker88>. I have also worked on several tools to support program comprehension, including the Whyline, which automates many of the more challenging aspects of navigating dependencies in code, and visualizes them <ko09>: Code editors, development environments, and program comprehension tools can also be helpful. Early evidence showed that simple features like syntax highlighting and careful typographic choices can improve the speed of program comprehension<baecker88>. I have also worked on several tools to support program comprehension, including the Whyline, which automates many of the more challenging aspects of navigating dependencies in code, and visualizes them<ko09>:
|https://www.youtube.com/embed/pbElN8nfe3k|The Whyline for Java|The Whyline for Java, a debugging tool that faciliates dependency navigation|Amy J. Ko] |https://www.youtube.com/embed/pbElN8nfe3k|The Whyline for Java|The Whyline for Java, a debugging tool that faciliates dependency navigation|Amy J. Ko]

View file

@ -4,7 +4,7 @@ Nowhere is this constant evolution more apparent then in our daily encounters wi
To manage change, developers use many kinds of tools and practices. To manage change, developers use many kinds of tools and practices.
One of the most common ways of managing change is to *refactor* code. Refactoring helps developers modify the _architecture_ of a program while keeping its behavior the same, enabling them to implement or modify functionality more easily. For example, one of the most common and simple refactorings is to rename a variable (renaming its definition and all of its uses). This doesn't change the architecture of a program at all, but does improve its readability. Other refactors can be more complex. For example, consider adding a new parameter to a function: all calls to that function need to pass that new parameter, which means you need to go through each call and decide on a value to send from that call site. Studies of refactoring in practice have found that refactorings can be big and small, that they don't always preserve the behavior of a program, and that developers perceive them as involving substantial costs and risks<kim12> One of the most common ways of managing change is to *refactor* code. Refactoring helps developers modify the _architecture_ of a program while keeping its behavior the same, enabling them to implement or modify functionality more easily. For example, one of the most common and simple refactorings is to rename a variable (renaming its definition and all of its uses). This doesn't change the architecture of a program at all, but does improve its readability. Other refactors can be more complex. For example, consider adding a new parameter to a function: all calls to that function need to pass that new parameter, which means you need to go through each call and decide on a value to send from that call site. Studies of refactoring in practice have found that refactorings can be big and small, that they don't always preserve the behavior of a program, and that developers perceive them as involving substantial costs and risks<kim12>.
Another fundamental way that developers manage change is *version control* systems. As you know, they help developers track changes to code, allowing them to revert, merge, fork, and clone projects in a way that is traceable and reliable. Version control systems also help developers identify merge conflicts, so that they don't accidentally override each others' work<nelson19>. While today the most popular version control system is Git, there are actually many types. Some are _centralized_, representing one single ground truth of a project's code, usually stored on a server. Commits to centralized repositories become immediately available to everyone else on a project. Other version control systems are _distributed_, such as Git, allowing one copy of a repository on every local machine. Commits to these local copies don't automatically go to everyone else; rather, they are pushed to some central copy, from which others can pull updates. Another fundamental way that developers manage change is *version control* systems. As you know, they help developers track changes to code, allowing them to revert, merge, fork, and clone projects in a way that is traceable and reliable. Version control systems also help developers identify merge conflicts, so that they don't accidentally override each others' work<nelson19>. While today the most popular version control system is Git, there are actually many types. Some are _centralized_, representing one single ground truth of a project's code, usually stored on a server. Commits to centralized repositories become immediately available to everyone else on a project. Other version control systems are _distributed_, such as Git, allowing one copy of a repository on every local machine. Commits to these local copies don't automatically go to everyone else; rather, they are pushed to some central copy, from which others can pull updates.

View file

@ -20,9 +20,9 @@ The first conference, the IBM 360 project, and Hamilton's experiences on the Apo
* What kinds of tools and languages can accelerate a programmers work and help them prevent mistakes? * What kinds of tools and languages can accelerate a programmers work and help them prevent mistakes?
* How can projects not lose sight of the immense complexity of human needs, values, ethics, and policy that interact with engineering decisions? * How can projects not lose sight of the immense complexity of human needs, values, ethics, and policy that interact with engineering decisions?
As it became clear that software was not an incremental change in technology, but a profoundly disruptive one, countless communities began to explore these questions in research and practice. Black American entreprenuers began to explore how to use software to connect and build community well before the internet was ubiquitous, creating some of the first web-scale online communities and forging careers at IBM, ultimately to be suppressed by racism in the workplace and society <mcilwain19>. White entreprenuers in Silicon Valley began to explore ways to bring computing to the masses, bolstered by the immense capital investments of venture capitalists, who saw opportunities for profit through disruption <kenney00>. And academia, which had helped demonstrate the feasibility of computing and established its foundations, began to invent the foundational tools of software engineering including, version control systems, software testing, and a wide array of high-level programming languages such as Fortran <metcalf02>, LISP <mccarthy78>, C++ <stroustrup96> and Smalltalk <kay96>, all of which inspired the design of today's most popular languages, including Java, Python, and JavaScript. And throughout, despite the central role of women in programming the first digital computers, managing the first major software engineering projects, and imagining how software could change the world, women were systematically excluded from all of these efforts, their histories forgotten, erased, and overshadowed by pervasive sexism in commerce and government <abbate12>. As it became clear that software was not an incremental change in technology, but a profoundly disruptive one, countless communities began to explore these questions in research and practice. Black American entreprenuers began to explore how to use software to connect and build community well before the internet was ubiquitous, creating some of the first web-scale online communities and forging careers at IBM, ultimately to be suppressed by racism in the workplace and society<mcilwain19>. White entreprenuers in Silicon Valley began to explore ways to bring computing to the masses, bolstered by the immense capital investments of venture capitalists, who saw opportunities for profit through disruption<kenney00>. And academia, which had helped demonstrate the feasibility of computing and established its foundations, began to invent the foundational tools of software engineering including, version control systems, software testing, and a wide array of high-level programming languages such as Fortran<metcalf02>, LISP<mccarthy78>, C++<stroustrup96> and Smalltalk<kay96>, all of which inspired the design of today's most popular languages, including Java, Python, and JavaScript. And throughout, despite the central role of women in programming the first digital computers, managing the first major software engineering projects, and imagining how software could change the world, women were systematically excluded from all of these efforts, their histories forgotten, erased, and overshadowed by pervasive sexism in commerce and government<abbate12>.
While technical progress has been swift, progress on the _human_ aspects of software engineering, have been more difficult to understand and improve. One of the seminal books on these issues was Fred P. Brooks, Jr.'s _The Mythical Man Month_ <brooks95>. In it, he presented hundreds of claims about software engineering. For example, he hypothesized that adding more programmers to a project would actually make productivity _worse_ at some level, not better, because knowledge sharing would be an immense but necessary burden. He also claimed that the _first_ implementation of a solution is usually terrible and should be treated like a prototype: used for learning and then discarded. These and other claims have been the foundation of decades of years of research, all in search of some deeper answer to the questions above. And only recently have scholars begun to reveal how software and software engineering tends to encode, amplify, and reinforce existing structures and norms of discrimination by encoding it into data, algorithms, and software architectures <benjamin19>. These histories show that, just like any other human activity, there are strong cultural forces that shape how people engineer software together, what they engineer, and what affect that has on society. While technical progress has been swift, progress on the _human_ aspects of software engineering, have been more difficult to understand and improve. One of the seminal books on these issues was Fred P. Brooks, Jr.'s _The Mythical Man Month_<brooks95>. In it, he presented hundreds of claims about software engineering. For example, he hypothesized that adding more programmers to a project would actually make productivity _worse_ at some level, not better, because knowledge sharing would be an immense but necessary burden. He also claimed that the _first_ implementation of a solution is usually terrible and should be treated like a prototype: used for learning and then discarded. These and other claims have been the foundation of decades of years of research, all in search of some deeper answer to the questions above. And only recently have scholars begun to reveal how software and software engineering tends to encode, amplify, and reinforce existing structures and norms of discrimination by encoding it into data, algorithms, and software architectures<benjamin19>. These histories show that, just like any other human activity, there are strong cultural forces that shape how people engineer software together, what they engineer, and what affect that has on society.
If we step even further beyond software engineering as an activity and think more broadly about the role that software is playing in society today, there are also other, newer questions that we've only begun to answer. If every part of society now runs on code, what responsibility do software engineers have to ensure that code is right? What responsibility do software engineers have to avoid algorithmic bias? If our cars are to soon drive us around, who's responsible for the first death: the car, the driver, or the software engineers who built it, or the company that sold it? These ethical questions are in some ways the _future_ of software engineering, likely to shape its regulatory context, its processes, and its responsibilities. If we step even further beyond software engineering as an activity and think more broadly about the role that software is playing in society today, there are also other, newer questions that we've only begun to answer. If every part of society now runs on code, what responsibility do software engineers have to ensure that code is right? What responsibility do software engineers have to avoid algorithmic bias? If our cars are to soon drive us around, who's responsible for the first death: the car, the driver, or the software engineers who built it, or the company that sold it? These ethical questions are in some ways the _future_ of software engineering, likely to shape its regulatory context, its processes, and its responsibilities.

View file

@ -5,15 +5,15 @@ In designing the app, I made every imaginable software engineering mistake. I di
Now, ideally my "customer" would have reported any of these problems to me right away, and I would have learned some tough lessons about software engineering. But this customer was my best friend, and also a very nice guy. He wasn't about to trash all of my hard work. Instead, he suffered in silence. He struggled to install, struggled to use, and worst of all struggled to create. He produced some amazing art a few weeks after I gave him the app, but it was only after a few months of progress on our game that I learned he hadn't used my app for a single asset, preferring instead to suffer through Microsoft Paint. My app was too buggy, too slow, and too confusing to be useful. I was devastated. Now, ideally my "customer" would have reported any of these problems to me right away, and I would have learned some tough lessons about software engineering. But this customer was my best friend, and also a very nice guy. He wasn't about to trash all of my hard work. Instead, he suffered in silence. He struggled to install, struggled to use, and worst of all struggled to create. He produced some amazing art a few weeks after I gave him the app, but it was only after a few months of progress on our game that I learned he hadn't used my app for a single asset, preferring instead to suffer through Microsoft Paint. My app was too buggy, too slow, and too confusing to be useful. I was devastated.
Why didn't I know it was such a complete failure? *Because I wasn't looking*. I'd ignored the ultimate test suite: _my customer_. I'd learned that the only way to really know whether software requirements are right is by watching how it executes in the world through *monitoring* <turnbull16>. Why didn't I know it was such a complete failure? *Because I wasn't looking*. I'd ignored the ultimate test suite: _my customer_. I'd learned that the only way to really know whether software requirements are right is by watching how it executes in the world through *monitoring*<turnbull16>.
# Discovering Failures # Discovering Failures
Of course, this is easier said than done. That's because the (ideally) massive numbers of people executing your software is not easily observable <menzies13>. Moreover, each software quality you might want to monitor (performance, functional correctness, usability) requires entirely different methods of observation and analysis. Let's talk about some of the most important qualities to monitor and how to monitor them. Of course, this is easier said than done. That's because the (ideally) massive numbers of people executing your software is not easily observable<menzies13>. Moreover, each software quality you might want to monitor (performance, functional correctness, usability) requires entirely different methods of observation and analysis. Let's talk about some of the most important qualities to monitor and how to monitor them.
These are some of the easiest failures to detect because they are overt and unambiguous. Microsoft was one of the first organizations to do this comprehensively, building what eventually became known as Windows Error Reporting <glerum09>. It turns out that actually capturing these errors at scale and mining them for repeating, reproducible failures is quite complex, requiring classification, progressive data collection, and many statistical techniques to extract signal from noise. In fact, Microsoft has a dedicated team of data scientists and engineers whose sole job is to manage the error reporting infrastructure, monitor and triage incoming errors, and use trends in errors to make decisions about improvements to future releases and release processes. This is now standard practice in most companies and organizations, including other big software companies (Google, Apple, IBM, etc.), as well as open source projects (eg, Mozilla). In fact, many application development platforms now include this as a standard operating system feature. These are some of the easiest failures to detect because they are overt and unambiguous. Microsoft was one of the first organizations to do this comprehensively, building what eventually became known as Windows Error Reporting<glerum09>. It turns out that actually capturing these errors at scale and mining them for repeating, reproducible failures is quite complex, requiring classification, progressive data collection, and many statistical techniques to extract signal from noise. In fact, Microsoft has a dedicated team of data scientists and engineers whose sole job is to manage the error reporting infrastructure, monitor and triage incoming errors, and use trends in errors to make decisions about improvements to future releases and release processes. This is now standard practice in most companies and organizations, including other big software companies (Google, Apple, IBM, etc.), as well as open source projects (eg, Mozilla). In fact, many application development platforms now include this as a standard operating system feature.
Performance, like crashes, kernel panics, and hangs, is easily observable in software, but a bit trickier to characterize as good or bad. How slow is too slow? How bad is it if something is slow occasionally? You'll have to define acceptable thresholds for different use cases to be able to identify problems automatically. Some experts in industry <grabner16> still view this as an art. Performance, like crashes, kernel panics, and hangs, is easily observable in software, but a bit trickier to characterize as good or bad. How slow is too slow? How bad is it if something is slow occasionally? You'll have to define acceptable thresholds for different use cases to be able to identify problems automatically. Some experts in industry<grabner16> still view this as an art.
It's also hard to monitor performance without actually _harming_ performance. Many tools and web services (e.g., [New Relic|https://newrelic.com/]) are getting better at reducing this overhead and offering real time data about performance problems through sampling. It's also hard to monitor performance without actually _harming_ performance. Many tools and web services (e.g., [New Relic|https://newrelic.com/]) are getting better at reducing this overhead and offering real time data about performance problems through sampling.
@ -25,13 +25,13 @@ The biggest limitation of the monitoring above is that it only reveals _what_ pe
Usability problems and missing features, unlike some of the preceding problems, are even harder to detect or observe, because the only true indicator that something is hard to use is in a user's mind. That said, there are a couple of approaches to detecting the possibility of usability problems. Usability problems and missing features, unlike some of the preceding problems, are even harder to detect or observe, because the only true indicator that something is hard to use is in a user's mind. That said, there are a couple of approaches to detecting the possibility of usability problems.
One is by monitoring application usage. Assuming your users will tolerate being watched, there are many techniques: 1) automatically instrumenting applications for user interaction events, 2) mining events for problematic patterns, and 3) browsing and analyzing patterns for more subjective issues <ivory01>. Modern tools and services like make it easier to capture, store, and analyze this usage data, although they still require you to have some upfront intuition about what to monitor. More advanced, experimental techniques in research automatically analyze undo events as indicators of usability problems <akers09> this work observes that undo is often an indicator of a mistake in creative software, and mistakes are often indicators of usability problems. One is by monitoring application usage. Assuming your users will tolerate being watched, there are many techniques: 1) automatically instrumenting applications for user interaction events, 2) mining events for problematic patterns, and 3) browsing and analyzing patterns for more subjective issues<ivory01>. Modern tools and services like make it easier to capture, store, and analyze this usage data, although they still require you to have some upfront intuition about what to monitor. More advanced, experimental techniques in research automatically analyze undo events as indicators of usability problems<akers09> this work observes that undo is often an indicator of a mistake in creative software, and mistakes are often indicators of usability problems.
All of the usage data above can tell you _what_ your users are doing, but not _why_. For this, you'll need to get explicit feedback from support tickets, support forums, product reviews, and other critiques of user experience. Some of these types of reports go directly to engineering teams, becoming part of bug reporting systems, while others end up in customer service or marketing departments. While all of this data is valuable for monitoring user experience, most companies still do a bad job of using anything but bug reports to improve user experience, overlooking the rich insights in customer service interactions <chilana11>. All of the usage data above can tell you _what_ your users are doing, but not _why_. For this, you'll need to get explicit feedback from support tickets, support forums, product reviews, and other critiques of user experience. Some of these types of reports go directly to engineering teams, becoming part of bug reporting systems, while others end up in customer service or marketing departments. While all of this data is valuable for monitoring user experience, most companies still do a bad job of using anything but bug reports to improve user experience, overlooking the rich insights in customer service interactions<chilana11>.
Although bug reports are widely used, they have significant problems as a way to monitor: for developers to fix a problem, they need detailed steps to reproduce the problem, or stack traces or other state to help them track down the cause of a problem <bettenburg08>; these are precisely the kinds of information that are hard for users to find and submit, given that most people aren't trained to produce reliable, precise information for failure reproduction. Additionally, once the information is recorded in a bug report, even _interpreting_ the information requires social, organizational, and technical knowledge, meaning that if a problem is not addressed soon, an organization's ability to even interpret what the failure was and what caused it can decay over time <aranda09>. All of these issues can lead to intractable debugging challenges <qureshi16>. Although bug reports are widely used, they have significant problems as a way to monitor: for developers to fix a problem, they need detailed steps to reproduce the problem, or stack traces or other state to help them track down the cause of a problem<bettenburg08>; these are precisely the kinds of information that are hard for users to find and submit, given that most people aren't trained to produce reliable, precise information for failure reproduction. Additionally, once the information is recorded in a bug report, even _interpreting_ the information requires social, organizational, and technical knowledge, meaning that if a problem is not addressed soon, an organization's ability to even interpret what the failure was and what caused it can decay over time<aranda09>. All of these issues can lead to intractable debugging challenges<qureshi16>.
Larger software organizations now employ data scientists to help mitigate these challenges of analyzing and maintaining monitoring data and bug reports. Most of them try to answer questions such as <begel14>: Larger software organizations now employ data scientists to help mitigate these challenges of analyzing and maintaining monitoring data and bug reports. Most of them try to answer questions such as<begel14>:
* "How do users typically use my application?" * "How do users typically use my application?"
* "What parts of a software product are most used and/or loved by customers?" * "What parts of a software product are most used and/or loved by customers?"
@ -39,6 +39,6 @@ Larger software organizations now employ data scientists to help mitigate these
* "What are the common patterns of execution in my application?" * "What are the common patterns of execution in my application?"
* "How well does test coverage correspond to actual code usage by our customers?" * "How well does test coverage correspond to actual code usage by our customers?"
The most mature data science roles in software engineering teams even have multiple distinct roles, including _Insight Providers_, who gather and analyze data to inform decisions, _Modeling Specialists_, who use their machine learning expertise to build predictive models, _Platform Builders_, who create the infrastructure necessary for gathering data <kim16>. Of course, smaller organizations may have individuals who take on all of these roles. Moreover, not all ways of discovering missing requirements are data science roles. Many companies, for example, have customer experience specialists and community managers, who are less interested in data about experiences and more interested in directly communicating with customers about their experiences. These relational forms of monitoring can be much more effective at revealing software quality issues that aren't as easily observed, such as issues of racial or sexual bias in software or other forms of structural injustices built into the architecture of software. The most mature data science roles in software engineering teams even have multiple distinct roles, including _Insight Providers_, who gather and analyze data to inform decisions, _Modeling Specialists_, who use their machine learning expertise to build predictive models, _Platform Builders_, who create the infrastructure necessary for gathering data<kim16>. Of course, smaller organizations may have individuals who take on all of these roles. Moreover, not all ways of discovering missing requirements are data science roles. Many companies, for example, have customer experience specialists and community managers, who are less interested in data about experiences and more interested in directly communicating with customers about their experiences. These relational forms of monitoring can be much more effective at revealing software quality issues that aren't as easily observed, such as issues of racial or sexual bias in software or other forms of structural injustices built into the architecture of software.
All of this effort to capture and maintain user feedback can be messy to analyze because it usually comes in the form of natural language text. Services like [AnswerDash|http://answerdash.com] (a company I co-founded) structure this data by organizing requests around frequently asked questions. AnswerDash imposes a little widget on every page in a web application, making it easy for users to submit questions and find answers to previously asked questions. This generates data about the features and use cases that are leading to the most confusion, which types of users are having this confusion, and where in an application the confusion is happening most frequently. This product was based on several years of research in my lab <chilana13>. All of this effort to capture and maintain user feedback can be messy to analyze because it usually comes in the form of natural language text. Services like [AnswerDash|http://answerdash.com] (a company I co-founded) structure this data by organizing requests around frequently asked questions. AnswerDash imposes a little widget on every page in a web application, making it easy for users to submit questions and find answers to previously asked questions. This generates data about the features and use cases that are leading to the most confusion, which types of users are having this confusion, and where in an application the confusion is happening most frequently. This product was based on several years of research in my lab<chilana13>.

View file

@ -4,7 +4,7 @@ What you _can't_ see is just how much _complexity_ underlies this work. You can'
Organizations are a much bigger topic than I could possibly address here. To deeply understand them, you'd need to learn about [organizational studies|https://en.wikipedia.org/wiki/Organizational_studies], [organizational behavior|https://en.wikipedia.org/wiki/Organizational_behavior], [information systems|https://en.wikipedia.org/wiki/Information_system], and business in general. Organizations are a much bigger topic than I could possibly address here. To deeply understand them, you'd need to learn about [organizational studies|https://en.wikipedia.org/wiki/Organizational_studies], [organizational behavior|https://en.wikipedia.org/wiki/Organizational_behavior], [information systems|https://en.wikipedia.org/wiki/Information_system], and business in general.
The subset of this knowledge that's critical to understand about software engineering is limited to a few important concepts. The first and most important concept is that even in software organizations, the point of the company is rarely to make software; it's to provide *value* <osterwalder15>. Software is sometimes the central means to providing that value, but more often than not, it's the _information_ flowing through that software that's the truly valuable piece. [Requirements|requirements], which we will discuss in a later chapter, help engineers organize how software will provide value. The subset of this knowledge that's critical to understand about software engineering is limited to a few important concepts. The first and most important concept is that even in software organizations, the point of the company is rarely to make software; it's to provide *value*<osterwalder15>. Software is sometimes the central means to providing that value, but more often than not, it's the _information_ flowing through that software that's the truly valuable piece. [Requirements|requirements], which we will discuss in a later chapter, help engineers organize how software will provide value.
The individuals in a software organization take on different roles to achieve that value. These roles are sometimes spread across different people and sometimes bundled up into one person, depending on how the organization is structured, but the roles are always there. Let's go through each one in detail so you understand how software engineers relate to each role. The individuals in a software organization take on different roles to achieve that value. These roles are sometimes spread across different people and sometimes bundled up into one person, depending on how the organization is structured, but the roles are always there. Let's go through each one in detail so you understand how software engineers relate to each role.
@ -15,7 +15,7 @@ The individuals in a software organization take on different roles to achieve th
* *Sales* takes the product that's been built and try to sell it to the audiences that marketers have identified. They also try to refine an organization's understanding of what the customer wants and needs, providing feedback to marketing, product, and design, which engineers then address. * *Sales* takes the product that's been built and try to sell it to the audiences that marketers have identified. They also try to refine an organization's understanding of what the customer wants and needs, providing feedback to marketing, product, and design, which engineers then address.
* *Support* helps the people using the product to use it successfully and, like sales, provides feedback to product, design, and engineering about the product's value (or lack thereof) and it's defects. * *Support* helps the people using the product to use it successfully and, like sales, provides feedback to product, design, and engineering about the product's value (or lack thereof) and it's defects.
As I noted above, sometimes the roles above get merged into individuals. When I was CTO at AnswerDash, I had software engineering roles, design roles, product roles, sales roles, _and_ support roles. This was partly because it was a small company when I was there. As organizations grow, these roles tend to be divided into smaller pieces. This division often means that different parts of the organization don't share knowledge, even when it would be advantageous <chilana11>. As I noted above, sometimes the roles above get merged into individuals. When I was CTO at AnswerDash, I had software engineering roles, design roles, product roles, sales roles, _and_ support roles. This was partly because it was a small company when I was there. As organizations grow, these roles tend to be divided into smaller pieces. This division often means that different parts of the organization don't share knowledge, even when it would be advantageous<chilana11>.
Note that in the division of responsibilities above, software engineers really aren't the designers by default. They don't decide what product is made or what problems that product solves. They may have opinions&mdash;and a great deal of power to enforce their opinions, as the people building the product&mdash;but it's not ultimately their decision. Note that in the division of responsibilities above, software engineers really aren't the designers by default. They don't decide what product is made or what problems that product solves. They may have opinions&mdash;and a great deal of power to enforce their opinions, as the people building the product&mdash;but it's not ultimately their decision.
@ -26,15 +26,15 @@ There are other roles you might be thinking of that I haven't mentioned:
* *Researchers*, also called user researchers, also help people in a software organization make decisions, but usually _product_ decisions, helping marketers, sales, and product managers decide what products to make and who would want them. In many cases, they can complement the work of data scientists, [providing qualitative work to triangulate quantitative data|https://www.linkedin.com/pulse/ux-research-analytics-yann-riche?trk=prof-post]. * *Researchers*, also called user researchers, also help people in a software organization make decisions, but usually _product_ decisions, helping marketers, sales, and product managers decide what products to make and who would want them. In many cases, they can complement the work of data scientists, [providing qualitative work to triangulate quantitative data|https://www.linkedin.com/pulse/ux-research-analytics-yann-riche?trk=prof-post].
* *Ethics and policy specialists*, who might come with backgrounds in law, policy, or social science, might shape terms of service, software licenses, algorithmic bias audits, privacy policy compliance, and processes for engaging with stakeholders affected by the software being engineered. Any company that works with data, especially those that work with data at large scales or in contexts with great potential for harm, hate, and abuse, needs significant expertise to anticipate and prevent harm from engineering and design decisions. * *Ethics and policy specialists*, who might come with backgrounds in law, policy, or social science, might shape terms of service, software licenses, algorithmic bias audits, privacy policy compliance, and processes for engaging with stakeholders affected by the software being engineered. Any company that works with data, especially those that work with data at large scales or in contexts with great potential for harm, hate, and abuse, needs significant expertise to anticipate and prevent harm from engineering and design decisions.
Every decision made in a software team is under uncertainty, and so another important concept in organizations is *risk* <boehm91>. It's rarely possible to predict the future, and so organizations must take risks. Much of an organization's function is to mitigate the consequences of risks. Data scientists and researchers mitigate risk by increasing confidence in an organization's understanding of the market and its consumers. Engineers manage risk by trying to avoid defects. Of course, as many popular outlets on software engineering have begun to discover, when software fails, it usually "did exactly what it was told to do. The reason it failed is that it was told to do the wrong thing. <somers17> Every decision made in a software team is under uncertainty, and so another important concept in organizations is *risk*<boehm91>. It's rarely possible to predict the future, and so organizations must take risks. Much of an organization's function is to mitigate the consequences of risks. Data scientists and researchers mitigate risk by increasing confidence in an organization's understanding of the market and its consumers. Engineers manage risk by trying to avoid defects. Of course, as many popular outlets on software engineering have begun to discover, when software fails, it usually "did exactly what it was told to do. The reason it failed is that it was told to do the wrong thing.<somers17>
Open source communities are organizations too. The core activities of design, engineering, and support still exist in these, but how much a community is engaged in marketing and sales depends entirely on the purpose of the community. Big, established open source projects like [Mozilla|https://mozilla.org] have revenue, buildings, and a CEO, and while they don't sell anything, they do market. Others like Linux <lee03> rely heavily on contributions both from volunteers <ye03>, but also paid employees from companies that depend on Linux, like IBM, Google, and others. In these settings, there are still all of the challenges that come with software engineering, but fewer of the constraints that come from a for-profit or non-profit motive. In fact, recent work empirically uncovered 9 reasons why modern open source projects fail: 1) lost to competition, 2) made obsolete by technology advances, 3) lack of time to volunteer, 4) lack of interest by contributors, 5) outdated technologies, 6) poor maintainability, 7) interpersonal conflicts amongst developers, 8) legal challenges, 9) and acquisition <coelho17>. Another study showed that funding open source projects often requires substantial donations from large corporations; most projects don't ask for donations, and those that do receive very little, unless well-established, and most of those funds go to paying for basic expenses such as engineering salaries <overney20>. Those aren't too different from traditional software organizations, aside from the added challenges of sustaining a volunteer workforce. Open source communities are organizations too. The core activities of design, engineering, and support still exist in these, but how much a community is engaged in marketing and sales depends entirely on the purpose of the community. Big, established open source projects like [Mozilla|https://mozilla.org] have revenue, buildings, and a CEO, and while they don't sell anything, they do market. Others like Linux<lee03> rely heavily on contributions both from volunteers<ye03>, but also paid employees from companies that depend on Linux, like IBM, Google, and others. In these settings, there are still all of the challenges that come with software engineering, but fewer of the constraints that come from a for-profit or non-profit motive. In fact, recent work empirically uncovered 9 reasons why modern open source projects fail: 1) lost to competition, 2) made obsolete by technology advances, 3) lack of time to volunteer, 4) lack of interest by contributors, 5) outdated technologies, 6) poor maintainability, 7) interpersonal conflicts amongst developers, 8) legal challenges, 9) and acquisition<coelho17>. Another study showed that funding open source projects often requires substantial donations from large corporations; most projects don't ask for donations, and those that do receive very little, unless well-established, and most of those funds go to paying for basic expenses such as engineering salaries<overney20>. Those aren't too different from traditional software organizations, aside from the added challenges of sustaining a volunteer workforce.
All of the above has some important implications for what it means to be a software engineer: All of the above has some important implications for what it means to be a software engineer:
* Engineers are not the only important role in a software organization. In fact, they may be less important to an organization's success than other roles because the decisions they make (how to implement requirements) have smaller impact on the organization's goals than other decisions (what to make, who to sell it to, etc.). * Engineers are not the only important role in a software organization. In fact, they may be less important to an organization's success than other roles because the decisions they make (how to implement requirements) have smaller impact on the organization's goals than other decisions (what to make, who to sell it to, etc.).
* Engineers have to work with _a lot_ of people working with different roles. Learning what those roles are and what shapes their success is important to being a good collaborator <li17>. * Engineers have to work with _a lot_ of people working with different roles. Learning what those roles are and what shapes their success is important to being a good collaborator<li17>.
* While engineers might have many great ideas for product, if they really want to shape what they're building, they should be in a product role, not an engineering role. * While engineers might have many great ideas for product, if they really want to shape what they're building, they should be in a product role, not an engineering role.
All that said, without engineers, products wouldn't exist. They ensure that every detail about a product reflects the best knowledge of the people in their organization, and so attention to detail is paramount. In future chapters, we'll discuss all of the ways that software engineers manage this detail, mitigating the burden on their memories with tools and processes. All that said, without engineers, products wouldn't exist. They ensure that every detail about a product reflects the best knowledge of the people in their organization, and so attention to detail is paramount. In future chapters, we'll discuss all of the ways that software engineers manage this detail, mitigating the burden on their memories with tools and processes.

View file

@ -1,26 +1,26 @@
So you know what you're going to build and how you're going to build it. What process should you go about building it? Who's going to build what? What order should you build it in? How do you make sure everyone is in sync while you're building it? <pettersen16> And most importantly, how to do you make sure you build well and on time? These are fundamental questions in software engineering with many potential answers. Unfortunately, we still don't know which of those answers are right. So you know what you're going to build and how you're going to build it. What process should you go about building it? Who's going to build what? What order should you build it in? How do you make sure everyone is in sync while you're building it?<pettersen16> And most importantly, how to do you make sure you build well and on time? These are fundamental questions in software engineering with many potential answers. Unfortunately, we still don't know which of those answers are right.
At the foundation of all of these questions are basic matters of [project management|https://en.wikipedia.org/wiki/Project_management]: plan, execute, and monitor. But developers in the 1970's and on found that traditional project management ideas didn't seem to work. The earliest process ideas followed a "waterfall" model, in which a project begins by identifying requirements, writing specifications, implementing, testing, and releasing, all under the assumption that every stage could be fully tested and verified. (Recognize this? It's the order of topics we're discussing in this class!). Many managers seemed to like the waterfall model because it seemed structured and predictable; however, because most managers were originally software developers, they preferred a structured approach to project management <weinberg82>. The reality, however, was that no matter how much verification one did of each of these steps, there always seemed to be more information in later steps that caused a team to reconsider it's earlier decision (e.g., imagine a customer liked a requirement when it was described in the abstract, but when it was actually built, they rejected it, because they finally saw what the requirement really meant). At the foundation of all of these questions are basic matters of [project management|https://en.wikipedia.org/wiki/Project_management]: plan, execute, and monitor. But developers in the 1970's and on found that traditional project management ideas didn't seem to work. The earliest process ideas followed a "waterfall" model, in which a project begins by identifying requirements, writing specifications, implementing, testing, and releasing, all under the assumption that every stage could be fully tested and verified. (Recognize this? It's the order of topics we're discussing in this class!). Many managers seemed to like the waterfall model because it seemed structured and predictable; however, because most managers were originally software developers, they preferred a structured approach to project management<weinberg82>. The reality, however, was that no matter how much verification one did of each of these steps, there always seemed to be more information in later steps that caused a team to reconsider it's earlier decision (e.g., imagine a customer liked a requirement when it was described in the abstract, but when it was actually built, they rejected it, because they finally saw what the requirement really meant).
In 1988, Barry Boehm proposed an alternative to waterfall called the *Spiral model* <boehm88>: rather than trying to verify every step before proceeding to the next level of detail, _prototype_ every step along the way, getting partial validation, iteratively converging through a series of prototypes toward both an acceptable set of requirements _and_ an acceptable product. Throughout, risk assessment is key, encouraging a team to reflect and revise process based on what they are learning. What was important about these ideas were not the particulars of Boehm's proposed process, but the disruptive idea that iteration and process improvement are critical to engineering great software. In 1988, Barry Boehm proposed an alternative to waterfall called the *Spiral model*<boehm88>: rather than trying to verify every step before proceeding to the next level of detail, _prototype_ every step along the way, getting partial validation, iteratively converging through a series of prototypes toward both an acceptable set of requirements _and_ an acceptable product. Throughout, risk assessment is key, encouraging a team to reflect and revise process based on what they are learning. What was important about these ideas were not the particulars of Boehm's proposed process, but the disruptive idea that iteration and process improvement are critical to engineering great software.
|spiral.png|A spiral, showing successive rounds of prototping and risk analysis.|Boehm's spiral model of software development.|Boehm| |spiral.png|A spiral, showing successive rounds of prototping and risk analysis.|Boehm's spiral model of software development.|Boehm|
Around the same time, two influential books were published. Fred Brooks wrote *The Mythical Man Month* <brooks95>, a book about software project management, full of provocative ideas that would be tested over the next three decades, including the idea that adding more people to a project would not necessarily increase productivity. Tom DeMarco and Timothy Lister wrote another famous book, *Peopleware: Productive Projects and Teams* <demarco87> arguing that the major challenges in software engineering are human, not technical. Both of these works still represent some of the most widely-read statements of the problem of managing software development. Around the same time, two influential books were published. Fred Brooks wrote *The Mythical Man Month*<brooks95>, a book about software project management, full of provocative ideas that would be tested over the next three decades, including the idea that adding more people to a project would not necessarily increase productivity. Tom DeMarco and Timothy Lister wrote another famous book, *Peopleware: Productive Projects and Teams*<demarco87> arguing that the major challenges in software engineering are human, not technical. Both of these works still represent some of the most widely-read statements of the problem of managing software development.
These early ideas in software project management led to a wide variety of other discoveries about process. For example, organizations of all sizes can improve their process if they are very aware of what the people in the organization know, what it's capable of learning, and if it builds robust processes to actually continually improve process <dybå02,dybå03>. This might mean monitoring the pace of work, incentivizing engineers to reflect on inefficiencies in process, and teaching engineers how to be comfortable with process change. These early ideas in software project management led to a wide variety of other discoveries about process. For example, organizations of all sizes can improve their process if they are very aware of what the people in the organization know, what it's capable of learning, and if it builds robust processes to actually continually improve process<dybå02,dybå03>. This might mean monitoring the pace of work, incentivizing engineers to reflect on inefficiencies in process, and teaching engineers how to be comfortable with process change.
Beyond process improvement, other factors emerged. For example, researchers discovered that critical to team productivity was *awareness* of teammates' work <ko07>. Teams need tools like dashboards to help make them aware of changing priorities and tools like feeds to coordinate short term work <treude10>. Moreover, researchers found that engineers tended to favor non-social sources such as documentation for factual information, but social sources for information to support problem solving <milewski07>. Decades ago, developers used tools like email and IRC for awareness; now they use tools like [Slack|https://slack.com], [Trello|https://trello.com/], [GitHub|http://github.com], and [JIRA|https://www.atlassian.com/software/jira], which have the same basic functionality, but are much more polished, streamlined, and customizable. Beyond process improvement, other factors emerged. For example, researchers discovered that critical to team productivity was *awareness* of teammates' work<ko07>. Teams need tools like dashboards to help make them aware of changing priorities and tools like feeds to coordinate short term work<treude10>. Moreover, researchers found that engineers tended to favor non-social sources such as documentation for factual information, but social sources for information to support problem solving<milewski07>. Decades ago, developers used tools like email and IRC for awareness; now they use tools like [Slack|https://slack.com], [Trello|https://trello.com/], [GitHub|http://github.com], and [JIRA|https://www.atlassian.com/software/jira], which have the same basic functionality, but are much more polished, streamlined, and customizable.
In addition to awareness, *ownership* is a critical idea in process. This is the idea that for every line of code, someone is responsible for it's quality. The owner _might_ be the person who originally wrote the code, but it could also shift to new team members. Studies of code ownership on Windows Vista and Windows 7 found that less a component had a clear owner, the more pre-release defects it had and the more post-release failures were reported by users <bird11>. This means that in addition to getting code written, having clear ownership and clear processes for transfer of ownership are key to functional correctness. In addition to awareness, *ownership* is a critical idea in process. This is the idea that for every line of code, someone is responsible for it's quality. The owner _might_ be the person who originally wrote the code, but it could also shift to new team members. Studies of code ownership on Windows Vista and Windows 7 found that less a component had a clear owner, the more pre-release defects it had and the more post-release failures were reported by users<bird11>. This means that in addition to getting code written, having clear ownership and clear processes for transfer of ownership are key to functional correctness.
*Pace* is another factor that affects quality. Clearly, there's a tradeoff between how fast a team works and the quality of the product it can build. In fact, interview studies of engineers at Google, Facebook, Microsoft, Intel, and other large companies found that the pressure to reduce "time to market" harmed nearly every aspect of teamwork: the availability and discoverability of information, clear communication, planning, integration with others' work, and code ownership <rubin16>. Not only did a fast pace reduce quality, but it also reduced engineers' personal satisfaction with their job and their work. I encountered similar issues as CTO of my startup: while racing to market, I was often asked to meet impossible deadlines with zero defects and had to constantly communicate to the other executives in the company why this was not possible <ko17>. *Pace* is another factor that affects quality. Clearly, there's a tradeoff between how fast a team works and the quality of the product it can build. In fact, interview studies of engineers at Google, Facebook, Microsoft, Intel, and other large companies found that the pressure to reduce "time to market" harmed nearly every aspect of teamwork: the availability and discoverability of information, clear communication, planning, integration with others' work, and code ownership<rubin16>. Not only did a fast pace reduce quality, but it also reduced engineers' personal satisfaction with their job and their work. I encountered similar issues as CTO of my startup: while racing to market, I was often asked to meet impossible deadlines with zero defects and had to constantly communicate to the other executives in the company why this was not possible<ko17>.
Because of the importance of awareness and communication, the *distance* between teammates is also a critical factor. This is most visible in companies that hire remote developers, building distributed teams, or when teams are fully distributed (such as when there is a pandemic requiring social distancing). One motivation for doing this is to reduce costs or gain access to engineering talent that is distant from a team's geographical center, but over time, companies have found that doing so necessitates significant investments in socialization to ensure quality, minimizing geographical, temporal and cultural separation <smite10>. Researchers have found that there appear to be fundamental tradeoffs between productivity, quality, and/or profits in these settings <ramasubbu11>. For example, more distance appears to lead to slower communication <wagstrom14>. Despite these tradeoffs, most rigorous studies of the cost of distributed development have found that when companies work hard to minimize temporal and cultural separation, the actual impact on defects was small <kocaguneli13>. These efforts to minimize separation include more structured onboarding practices, more structured communication, and more structured processes, as well as systematic efforts to build and maintain trusting social relationships. Some researchers have begun to explore even more extreme models of distributed development, hiring contract developers to complete microtasks over a few days without hiring them as employees; early studies suggest that these models have the worst of outcomes, with greater costs, poor scalability, and more significant quality issues <stol14>. Because of the importance of awareness and communication, the *distance* between teammates is also a critical factor. This is most visible in companies that hire remote developers, building distributed teams, or when teams are fully distributed (such as when there is a pandemic requiring social distancing). One motivation for doing this is to reduce costs or gain access to engineering talent that is distant from a team's geographical center, but over time, companies have found that doing so necessitates significant investments in socialization to ensure quality, minimizing geographical, temporal and cultural separation<smite10>. Researchers have found that there appear to be fundamental tradeoffs between productivity, quality, and/or profits in these settings<ramasubbu11>. For example, more distance appears to lead to slower communication<wagstrom14>. Despite these tradeoffs, most rigorous studies of the cost of distributed development have found that when companies work hard to minimize temporal and cultural separation, the actual impact on defects was small<kocaguneli13>. These efforts to minimize separation include more structured onboarding practices, more structured communication, and more structured processes, as well as systematic efforts to build and maintain trusting social relationships. Some researchers have begun to explore even more extreme models of distributed development, hiring contract developers to complete microtasks over a few days without hiring them as employees; early studies suggest that these models have the worst of outcomes, with greater costs, poor scalability, and more significant quality issues<stol14>.
A critical part of ensuring all that a team is successful is having someone responsible for managing these factors of distance, pace, ownership, awareness, and overall process. The most obvious person to oversee this is, of course, a project manager <borozdin17,norris17>. Research on what skills software engineering project managers need suggests that while some technical knowledge is necessary, it the soft skills necessary for managing all of these factors in communication and coordination that distinguish great managers <kalliamvakou17>. A critical part of ensuring all that a team is successful is having someone responsible for managing these factors of distance, pace, ownership, awareness, and overall process. The most obvious person to oversee this is, of course, a project manager<borozdin17,norris17>. Research on what skills software engineering project managers need suggests that while some technical knowledge is necessary, it the soft skills necessary for managing all of these factors in communication and coordination that distinguish great managers<kalliamvakou17>.
While all of this research has strong implications for practice, industry has largely explored its own ideas about process, devising frameworks that addressed issues of distance, pace, ownership, awareness, and process improvement. Extreme Programming <beck99> was one of these frameworks and it was full of ideas: While all of this research has strong implications for practice, industry has largely explored its own ideas about process, devising frameworks that addressed issues of distance, pace, ownership, awareness, and process improvement. Extreme Programming<beck99> was one of these frameworks and it was full of ideas:
* Be iterative * Be iterative
* Do small releases * Do small releases
@ -33,11 +33,11 @@ While all of this research has strong implications for practice, industry has la
* Use an open workspace * Use an open workspace
* Work sane hours * Work sane hours
Note that none of these had any empirical evidence to back them. Moreover, Beck described in his original proposal that these ideas were best for "_outsourced or in-house development of small- to medium-sized systems where requirements are vague and likely to change_", but as industry often does, it began hyping it as a universal solution to software project management woes and adopted all kinds of combinations of these ideas, adapting them to their existing processes. In reality, the value of XP appears to depend on highly project-specific factors <müller13>, while the core ideas that industry has adopted are valuing feedback, communication, simplicity, and respect for individuals and the team <sharp04>. Researchers continue to investigate the merits of the list above; for example, numerous studies have investigated the effects of pair programming on defects, finding small but measurable benefits <dibella13>. Note that none of these had any empirical evidence to back them. Moreover, Beck described in his original proposal that these ideas were best for "_outsourced or in-house development of small- to medium-sized systems where requirements are vague and likely to change_", but as industry often does, it began hyping it as a universal solution to software project management woes and adopted all kinds of combinations of these ideas, adapting them to their existing processes. In reality, the value of XP appears to depend on highly project-specific factors<müller13>, while the core ideas that industry has adopted are valuing feedback, communication, simplicity, and respect for individuals and the team<sharp04>. Researchers continue to investigate the merits of the list above; for example, numerous studies have investigated the effects of pair programming on defects, finding small but measurable benefits<dibella13>.
At the same time, Beck began also espousing the idea of ["Agile" methods|http://agilemanifesto.org/], which celebrated many of the values underlying Extreme Programming, such as focusing on individuals, keeping things simple, collaborating with customers, and being iterative. This idea of begin agile was even more popular and spread widely in industry and research, even though many of the same ideas appeared much earlier in Boehm's work on the Spiral model. Researchers found that Agile methods can increase developer enthusiasm <syedabdullah06>, that agile teams need different roles such as Mentor, Co-ordinator, Translator, Champion, Promoter, and Terminator <hoda10>, and that teams are combing agile methods with all kinds of process ideas from other project management frameworks such as [Scrum|https://en.wikipedia.org/wiki/Scrum_(software_development)] (meet daily to plan work, plan two-week sprints, maintain a backlog of work) and Kanban (visualize the workflow, limit work-in-progress, manage flow, make policies explicit, and implement feedback loops) <albaik15>. Research has also found that transitioning a team to Agile methods is slow and complex because it requires everyone on a team to change their behavior, beliefs, and practices <hoda17>. At the same time, Beck began also espousing the idea of ["Agile" methods|http://agilemanifesto.org/], which celebrated many of the values underlying Extreme Programming, such as focusing on individuals, keeping things simple, collaborating with customers, and being iterative. This idea of begin agile was even more popular and spread widely in industry and research, even though many of the same ideas appeared much earlier in Boehm's work on the Spiral model. Researchers found that Agile methods can increase developer enthusiasm<syedabdullah06>, that agile teams need different roles such as Mentor, Co-ordinator, Translator, Champion, Promoter, and Terminator<hoda10>, and that teams are combing agile methods with all kinds of process ideas from other project management frameworks such as [Scrum|https://en.wikipedia.org/wiki/Scrum_(software_development)] (meet daily to plan work, plan two-week sprints, maintain a backlog of work) and Kanban (visualize the workflow, limit work-in-progress, manage flow, make policies explicit, and implement feedback loops)<albaik15>. Research has also found that transitioning a team to Agile methods is slow and complex because it requires everyone on a team to change their behavior, beliefs, and practices<hoda17>.
Ultimately, all of this energy around process ideas in industry is exciting, but disorganized. None of these efforts really get to the core of what makes software projects difficult to manage. One effort in research to get to this core by contributing new theories that explain these difficulties. The first is Herbsleb's *Socio-Technical Theory of Coordination (STTC)*. The idea of the theory is quite simple: _technical dependencies_ in engineering decisions (e.g., this function calls this other function, this data type stores this other data type) define the _social constraints_ that the organization must solve in a variety of ways to build and maintain software <herbsleb03,herbsleb16>. The better the organization builds processes and awareness tools to ensure that the people who own those engineering dependencies are communicating and aware of each others' work, the fewer defects that will occur. Herbsleb referred this alignment as _sociotechnical congruence_, and conducted a number of studies demonstrating its predictive and explanatory power. Ultimately, all of this energy around process ideas in industry is exciting, but disorganized. None of these efforts really get to the core of what makes software projects difficult to manage. One effort in research to get to this core by contributing new theories that explain these difficulties. The first is Herbsleb's *Socio-Technical Theory of Coordination (STTC)*. The idea of the theory is quite simple: _technical dependencies_ in engineering decisions (e.g., this function calls this other function, this data type stores this other data type) define the _social constraints_ that the organization must solve in a variety of ways to build and maintain software<herbsleb03,herbsleb16>. The better the organization builds processes and awareness tools to ensure that the people who own those engineering dependencies are communicating and aware of each others' work, the fewer defects that will occur. Herbsleb referred this alignment as _sociotechnical congruence_, and conducted a number of studies demonstrating its predictive and explanatory power.
I extended this idea to congruence with beliefs about _product_ value <ko17>, claiming that successful software products require the constant, collective communication and agreement of a coherent proposition of a product's value across UX, design, engineering, product, marketing, sales, support, and even customers. A team needs to achieve Herbsleb's sociotechnical congruence to have a successful product, but that alone is not enough: the rest of the organization has to have a consistent understanding of what is being built and why, even as that understanding evolves over time. I extended this idea to congruence with beliefs about _product_ value<ko17>, claiming that successful software products require the constant, collective communication and agreement of a coherent proposition of a product's value across UX, design, engineering, product, marketing, sales, support, and even customers. A team needs to achieve Herbsleb's sociotechnical congruence to have a successful product, but that alone is not enough: the rest of the organization has to have a consistent understanding of what is being built and why, even as that understanding evolves over time.

View file

@ -1,18 +1,18 @@
When we think of productivity, we usually have a vague concept of a rate of work per unit time. Where it gets tricky is in defining "work". On an individual level, work can be easier to define, because developers often have specific concrete tasks that they're assigned. But until they're not, it's not really easy to define progress (well, it's not that easy to define "done" sometimes either, but that's a topic for a later chapter). When you start considering work at the scale of a team or an organization, productivity gets even harder to define, since an individual's productivity might be increased by ignoring every critical request from a teammate, harming the team's overall productivity. When we think of productivity, we usually have a vague concept of a rate of work per unit time. Where it gets tricky is in defining "work". On an individual level, work can be easier to define, because developers often have specific concrete tasks that they're assigned. But until they're not, it's not really easy to define progress (well, it's not that easy to define "done" sometimes either, but that's a topic for a later chapter). When you start considering work at the scale of a team or an organization, productivity gets even harder to define, since an individual's productivity might be increased by ignoring every critical request from a teammate, harming the team's overall productivity.
Despite the challenge in defining productivity, there are numerous factors that affect productivity. For example, at the individual level, having the right tools can result in an order of magnitude difference in speed at accomplishing a task. One study I ran found that developers using the Eclipse IDE spent a third of their time just physically navigating between source files <ko05>. With the right navigation aids, developers could be writing code and fixing bugs 30% faster. In fact, some tools like Mylyn automatically bring relevant code to the developer rather than making them navigate to it, greatly increasing the speed which with developers can accomplish a task <kersten06>. Long gone are the days when developers should be using bare command lines and text editors to write code: IDEs can and do greatly increase productivity when used and configured with speed in mind. Despite the challenge in defining productivity, there are numerous factors that affect productivity. For example, at the individual level, having the right tools can result in an order of magnitude difference in speed at accomplishing a task. One study I ran found that developers using the Eclipse IDE spent a third of their time just physically navigating between source files<ko05>. With the right navigation aids, developers could be writing code and fixing bugs 30% faster. In fact, some tools like Mylyn automatically bring relevant code to the developer rather than making them navigate to it, greatly increasing the speed which with developers can accomplish a task<kersten06>. Long gone are the days when developers should be using bare command lines and text editors to write code: IDEs can and do greatly increase productivity when used and configured with speed in mind.
Of course, individual productivity is about more than just tools. Studies of workplace productivity show that developers have highly fragmented days, interrupted by meetings, emails, coding, and non-work distractions <meyer17>. These interruptions are often viewed negatively from an individual perspective <northrup16>, but may be highly valuable from a team and organizational perspective. And then, productivity is not just about skills to manage time, but also many other skills that shape developer expertise, including skills in designing architectures, debugging, testing, programming languages, etc. <baltes18>. Hiring is therefore about far more than just how quickly and effectively someone can code <bartram16>. Of course, individual productivity is about more than just tools. Studies of workplace productivity show that developers have highly fragmented days, interrupted by meetings, emails, coding, and non-work distractions<meyer17>. These interruptions are often viewed negatively from an individual perspective<northrup16>, but may be highly valuable from a team and organizational perspective. And then, productivity is not just about skills to manage time, but also many other skills that shape developer expertise, including skills in designing architectures, debugging, testing, programming languages, etc.<baltes18>. Hiring is therefore about far more than just how quickly and effectively someone can code<bartram16>.
That said, productivity is not just about individual developers. Because communication is a key part of team productivity, an individual's productivity is as much determined by their ability to collaborate and communicate with other developers. In a study spanning dozens of interviews with senior software engineers, Li et al. found that the majority of critical attributes for software engineering skill (productivity included) concerned their interpersonal skills, their communication skills, and their ability to be resourceful within their organization <li15>. Similarly, LaToza et al. found that the primary bottleneck in productivity was communication with teammates, primarily because waiting for replies was slower than just looking something up <latoza06>. Of course, looking something up has its own problems. While StackOverflow is an incredible resource for missing documentation <mamykina11>, it also is full of all kinds of misleading and incorrect information contributed by developers without sufficient expertise to answer questions <barua14>. Finally, because communication is such a critical part of retrieving information, adding more developers to a team has surprising effects. One study found that adding people to a team slowly enough to allow them to onboard effectively could reduce defects, but adding them too fast led to increases in defects <meneely11>. That said, productivity is not just about individual developers. Because communication is a key part of team productivity, an individual's productivity is as much determined by their ability to collaborate and communicate with other developers. In a study spanning dozens of interviews with senior software engineers, Li et al. found that the majority of critical attributes for software engineering skill (productivity included) concerned their interpersonal skills, their communication skills, and their ability to be resourceful within their organization<li15>. Similarly, LaToza et al. found that the primary bottleneck in productivity was communication with teammates, primarily because waiting for replies was slower than just looking something up<latoza06>. Of course, looking something up has its own problems. While StackOverflow is an incredible resource for missing documentation<mamykina11>, it also is full of all kinds of misleading and incorrect information contributed by developers without sufficient expertise to answer questions<barua14>. Finally, because communication is such a critical part of retrieving information, adding more developers to a team has surprising effects. One study found that adding people to a team slowly enough to allow them to onboard effectively could reduce defects, but adding them too fast led to increases in defects<meneely11>.
Another dimension of productivity is learning. Great engineers are resourceful, quick learners <li15>. New engineers must be even more resourceful, even though their instincts are often to hide their lack of expertise from exactly the people they need help from <begel08>. Experienced developers know that learning is important and now rely heavily on social media such as Twitter to follow industry changes, build learning relationships, and discover new concepts and platforms to learn <singer14>. And, of course, developers now rely heavily on web search to fill in inevitable gaps in their knowledge about APIs, error messages, and myriad other details about languages and platforms <xia17>. Another dimension of productivity is learning. Great engineers are resourceful, quick learners<li15>. New engineers must be even more resourceful, even though their instincts are often to hide their lack of expertise from exactly the people they need help from<begel08>. Experienced developers know that learning is important and now rely heavily on social media such as Twitter to follow industry changes, build learning relationships, and discover new concepts and platforms to learn<singer14>. And, of course, developers now rely heavily on web search to fill in inevitable gaps in their knowledge about APIs, error messages, and myriad other details about languages and platforms<xia17>.
Unfortunately, learning is no easy task. One of my earliest studies as a researcher investigated the barriers to learning new programming languages and systems, finding six distinct types of content that are challenging <ko04>. To use a programming platform successfully, people need to overcome _design _ barriers, which are the abstract computational problems that must be solved, independent of the languages and APIs. People need to overcome _selection _ barriers, which involve finding the right abstractions or APIs to achieve the design they have identified. People need to overcome _use _ and _coordination _ barriers, which involve operating and coordinating different parts of a language or API together to achieve novel functionality. People need to overcome _comprehension _ barriers, which involve knowing what can go wrong when using part of a language or API. And finally, people need to overcome _information _ barriers, which are posed by the limited ability of tools to inspect a program's behavior at runtime during debugging. Every single one of these barriers has its own challenges, and developers encounter them every time they are learning a new platform, regardless of how much expertise they have. Unfortunately, learning is no easy task. One of my earliest studies as a researcher investigated the barriers to learning new programming languages and systems, finding six distinct types of content that are challenging<ko04>. To use a programming platform successfully, people need to overcome _design _ barriers, which are the abstract computational problems that must be solved, independent of the languages and APIs. People need to overcome _selection _ barriers, which involve finding the right abstractions or APIs to achieve the design they have identified. People need to overcome _use _ and _coordination _ barriers, which involve operating and coordinating different parts of a language or API together to achieve novel functionality. People need to overcome _comprehension _ barriers, which involve knowing what can go wrong when using part of a language or API. And finally, people need to overcome _information _ barriers, which are posed by the limited ability of tools to inspect a program's behavior at runtime during debugging. Every single one of these barriers has its own challenges, and developers encounter them every time they are learning a new platform, regardless of how much expertise they have.
Aside from individual and team factors, productivity is also influenced by the particular features of a project's code, how the project is managed, or the environment and organizational culture in which developers work <vosburgh84,demarco85>. In fact, these might actually be the _biggest _ factors in determining developer productivity. This means that even a developer that is highly productive individually cannot rescue a team that is poorly structured working on poorly architected code. This might be why highly productive developers are so difficult to recruit to poorly managed teams. Aside from individual and team factors, productivity is also influenced by the particular features of a project's code, how the project is managed, or the environment and organizational culture in which developers work<vosburgh84,demarco85>. In fact, these might actually be the _biggest _ factors in determining developer productivity. This means that even a developer that is highly productive individually cannot rescue a team that is poorly structured working on poorly architected code. This might be why highly productive developers are so difficult to recruit to poorly managed teams.
A different way to think about productivity is to consider it from a "waste" perspective, in which waste is defined as any activity that does not contribute to a product's value to users or customers. Sedano et al. investigated this view across two years and eight software development projects in a software development consultancy <sedano17>, contributing a taxonomy of waste: A different way to think about productivity is to consider it from a "waste" perspective, in which waste is defined as any activity that does not contribute to a product's value to users or customers. Sedano et al. investigated this view across two years and eight software development projects in a software development consultancy<sedano17>, contributing a taxonomy of waste:
* *Building the wrong feature or product*. The cost of building a feature or product that does not address user or business needs. * *Building the wrong feature or product*. The cost of building a feature or product that does not address user or business needs.
* *Mismanaging the backlog*. The cost of duplicating work, expediting lower value user features, or delaying nec- essary bug fixes. * *Mismanaging the backlog*. The cost of duplicating work, expediting lower value user features, or delaying nec- essary bug fixes.

View file

@ -1,8 +1,8 @@
There are numerous ways a software project can fail: projects can be over budget, they can ship late, they can fail to be useful, or they can simply not be useful enough. Evidence clearly shows that success is highly contextual and stakeholder-dependent: success might be financial, social, physical and even emotional, suggesting that software engineering success is a multifaceted variable that cannot explained simply by user satisfaction, profitability or meeting requirements, budgets and schedules <ralph14>. There are numerous ways a software project can fail: projects can be over budget, they can ship late, they can fail to be useful, or they can simply not be useful enough. Evidence clearly shows that success is highly contextual and stakeholder-dependent: success might be financial, social, physical and even emotional, suggesting that software engineering success is a multifaceted variable that cannot explained simply by user satisfaction, profitability or meeting requirements, budgets and schedules<ralph14>.
One of the central reasons for this is that there are many distinct *software qualities* that software can have and depending on the stakeholders, each of these qualities might have more or less importance. For example, a safety critical system such as flight automation software should be reliable and defect-free, but it's okay if it's not particularly learnable&mdash;that's what training is for. A video game, however, should probably be fun and learnable, but it's fine if it ships with a few defects, as long as they don't interfere with fun <murphy14> One of the central reasons for this is that there are many distinct *software qualities* that software can have and depending on the stakeholders, each of these qualities might have more or less importance. For example, a safety critical system such as flight automation software should be reliable and defect-free, but it's okay if it's not particularly learnable&mdash;that's what training is for. A video game, however, should probably be fun and learnable, but it's fine if it ships with a few defects, as long as they don't interfere with fun<murphy14>
There are a surprisingly large number of software qualities <boehm76>. Many concern properties that are intrisinc to a software's implementation: There are a surprisingly large number of software qualities<boehm76>. Many concern properties that are intrisinc to a software's implementation:
* *Correctness* is the extent to which a program behaves according to its specification. If specifications are ambiguous, correctness is ambiguous. However, even if a specification is perfectly unambiguous, it might still fail to meet other qualities (e.g., a web site may be built as intended, but still be slow, unusable, and useless.) * *Correctness* is the extent to which a program behaves according to its specification. If specifications are ambiguous, correctness is ambiguous. However, even if a specification is perfectly unambiguous, it might still fail to meet other qualities (e.g., a web site may be built as intended, but still be slow, unusable, and useless.)
@ -28,11 +28,11 @@ Whereas the above qualities are concerned with how software behaves technically
Other qualities are concerned with the use of the software in the world by people: Other qualities are concerned with the use of the software in the world by people:
* *Learnability* is the easy with which a person can learn to operate software. Learnability is multi-dimensional and can be difficult to measure, including aspects of usability, expectations of prior knowledge, reliance on conventions, error proneness, and task alignment <grossman09>. * *Learnability* is the easy with which a person can learn to operate software. Learnability is multi-dimensional and can be difficult to measure, including aspects of usability, expectations of prior knowledge, reliance on conventions, error proneness, and task alignment<grossman09>.
* *User efficiency* is the speed with which a person can perform tasks with a program. For example, think about the speed with which you can navigate back to the table of contents of this book. Obviously, because most software supports many tasks, user efficiency isn't a single property of software, but one that varies depending on the task. * *User efficiency* is the speed with which a person can perform tasks with a program. For example, think about the speed with which you can navigate back to the table of contents of this book. Obviously, because most software supports many tasks, user efficiency isn't a single property of software, but one that varies depending on the task.
* *Accessibility* is the extent to which people with varying cognitive and motor abilities can operate the software as intended. For example, software that can only be used with a mouse is less accessible than something that can be used with a mouse, keyboard, or speech recognition. Software can be designed for all abilities, and even automatically adapted for individual abilities <wobbrock11>. * *Accessibility* is the extent to which people with varying cognitive and motor abilities can operate the software as intended. For example, software that can only be used with a mouse is less accessible than something that can be used with a mouse, keyboard, or speech recognition. Software can be designed for all abilities, and even automatically adapted for individual abilities<wobbrock11>.
* *Privacy* is the extent to which a system prevents access to information that intended for a particular audience or use. To achieve privacy, a system must be secure; for example, if anyone could log into your Facebook account, it would be insecure, and thus have poor privacy preservation. However, a secure system is not necessarily private: Facebook works hard on security, but shares immense amounts of private data with third parties, often without informed consent. * *Privacy* is the extent to which a system prevents access to information that intended for a particular audience or use. To achieve privacy, a system must be secure; for example, if anyone could log into your Facebook account, it would be insecure, and thus have poor privacy preservation. However, a secure system is not necessarily private: Facebook works hard on security, but shares immense amounts of private data with third parties, often without informed consent.
@ -46,6 +46,6 @@ Other qualities are concerned with the use of the software in the world by peopl
Although the lists above are not complete, you might have already noticed some tradeoffs between different qualities. A secure system is necessarily going to be less learnable, because there will be more to learn to operate it. A robust system will likely be less maintainable because it it will likely have more code to account for its diverse operating environments. Because one cannot achieve all software qualities, and achieving each quality takes significant time, it is necessary to prioritize qualities for each project. Although the lists above are not complete, you might have already noticed some tradeoffs between different qualities. A secure system is necessarily going to be less learnable, because there will be more to learn to operate it. A robust system will likely be less maintainable because it it will likely have more code to account for its diverse operating environments. Because one cannot achieve all software qualities, and achieving each quality takes significant time, it is necessary to prioritize qualities for each project.
These external notions of quality are not the only qualities that matter. For example, developers often view projects as successful if they offer intrinsically rewarding work <procaccino05>. That may sound selfish, but if developers _aren't_ enjoying their work, they're probably not going to achieve any of the qualities very well. Moreover, there are many organizational factors that can inhibit developers' ability to obtain these rewards. Project complexity, internal and external dependencies that are out of a developers control, process barriers, budget limitations, deadlines, poor HR planning, and pressure to ship can all interfere with project success <lavallee15>. These external notions of quality are not the only qualities that matter. For example, developers often view projects as successful if they offer intrinsically rewarding work<procaccino05>. That may sound selfish, but if developers _aren't_ enjoying their work, they're probably not going to achieve any of the qualities very well. Moreover, there are many organizational factors that can inhibit developers' ability to obtain these rewards. Project complexity, internal and external dependencies that are out of a developers control, process barriers, budget limitations, deadlines, poor HR planning, and pressure to ship can all interfere with project success<lavallee15>.
As I've noted before, the person most responsible for isolating developers from these organizational problems, and most responsible for prioritizing software qualities is a product manager. Check out the podcast below for one product manager's perspectives on the challenges of balancing these different priorities. As I've noted before, the person most responsible for isolating developers from these organizational problems, and most responsible for prioritizing software qualities is a product manager. Check out the podcast below for one product manager's perspectives on the challenges of balancing these different priorities.

View file

@ -2,17 +2,17 @@ Once you have a problem, a solution, and a design specification, it's entirely r
It depends. This mentality towards product design works fine if building and deploying something is cheap and getting feedback has no consequences. Simple consumer applications often benefit from this simplicity, especially early stage ones, because there's little to lose. For example, if you are starting a company, and do not even know if there is a market opportuniity yet, it may be worth quickly prototyping an idea, seeing if there's interesting, and then later thinking about how to carefully architect a product that meets that opportunity. This is [how products such as Facebook started|https://en.wikipedia.org/wiki/History_of_Facebook], with a poorly implemented prototype that revealed an opportunity, which was only later translated into a functional, reliable software service. It depends. This mentality towards product design works fine if building and deploying something is cheap and getting feedback has no consequences. Simple consumer applications often benefit from this simplicity, especially early stage ones, because there's little to lose. For example, if you are starting a company, and do not even know if there is a market opportuniity yet, it may be worth quickly prototyping an idea, seeing if there's interesting, and then later thinking about how to carefully architect a product that meets that opportunity. This is [how products such as Facebook started|https://en.wikipedia.org/wiki/History_of_Facebook], with a poorly implemented prototype that revealed an opportunity, which was only later translated into a functional, reliable software service.
However, what if prototyping a beta _isn't_ cheap to build? What if your product only has one shot at adoption? What if you're building something for a client and they want to define success? Worse yet, what if your product could _kill_ people if it's not built properly? Consider the [U.S. HealthCare.gov launch|https://en.wikipedia.org/wiki/HealthCare.gov], for example, which was lambasted for is countless defects and poor scalability at launch, only working for 1,100 simultaneous users, when 50,000 were exected and 250,000 actually arrived. To prevent disastrous launches like this, software teams have to be more careful about translating a design specification into a specific explicit set of goals that must be satisfied in order for the implementation to be complete. We call these goals *requirements* and we call this process of *requirements engineering* <sommerville97>. However, what if prototyping a beta _isn't_ cheap to build? What if your product only has one shot at adoption? What if you're building something for a client and they want to define success? Worse yet, what if your product could _kill_ people if it's not built properly? Consider the [U.S. HealthCare.gov launch|https://en.wikipedia.org/wiki/HealthCare.gov], for example, which was lambasted for is countless defects and poor scalability at launch, only working for 1,100 simultaneous users, when 50,000 were exected and 250,000 actually arrived. To prevent disastrous launches like this, software teams have to be more careful about translating a design specification into a specific explicit set of goals that must be satisfied in order for the implementation to be complete. We call these goals *requirements* and we call this process of *requirements engineering*<sommerville97>.
In principle, requirements are a relatively simple concept. They are simply statements of what must be true about a system to make the system acceptable. For example, suppose you were designing an interactive mobile game. You might want to write the requirement _The frame rate must never drop below 60 frames per second._ This could be important for any number of reasons: the game may rely on interactive speeds, your company's reputation may be for high fidelity graphics, or perhaps that high frame rate is key to creating a sense of realism. Or, imagine your game company has a reputation for high performance, high fidelity graphics, high frame rate graphics, and achieving any less would erode your company's brand. Whatever the reasons, expressing it as a requirement makes it explicit that any version of the software that doesn't meet that requirement is unacceptable, and sets a clear goal for engineering to meet. In principle, requirements are a relatively simple concept. They are simply statements of what must be true about a system to make the system acceptable. For example, suppose you were designing an interactive mobile game. You might want to write the requirement _The frame rate must never drop below 60 frames per second._ This could be important for any number of reasons: the game may rely on interactive speeds, your company's reputation may be for high fidelity graphics, or perhaps that high frame rate is key to creating a sense of realism. Or, imagine your game company has a reputation for high performance, high fidelity graphics, high frame rate graphics, and achieving any less would erode your company's brand. Whatever the reasons, expressing it as a requirement makes it explicit that any version of the software that doesn't meet that requirement is unacceptable, and sets a clear goal for engineering to meet.
The general idea of writing down requirements is actually a controversial one. Why not just discover what a system needs to do incrementally, through testing, user feedback, and other methods? Some of the original arguments for writing down requirements actually acknowledged that software is necessarily built incrementally, but that it is nevertheless useful to write down requirements from the outset <parnas86>. This is because requirements help you plan everything: what you have to build, what you have to test, and how to know when you're done. The theory is that by defining requirements explicitly, you plan, and by planning, you save time. The general idea of writing down requirements is actually a controversial one. Why not just discover what a system needs to do incrementally, through testing, user feedback, and other methods? Some of the original arguments for writing down requirements actually acknowledged that software is necessarily built incrementally, but that it is nevertheless useful to write down requirements from the outset<parnas86>. This is because requirements help you plan everything: what you have to build, what you have to test, and how to know when you're done. The theory is that by defining requirements explicitly, you plan, and by planning, you save time.
Do you really have to plan by _writing down_ requirements? For example, why not do what designers do, expressing requirements in the form of prototypes and mockups. These _implicitly_ state requirements, because they suggest what the software is supposed to do without saying it directly. But for some types of requirements, they actually imply nothing. For example, how responsive should a web page be to be? A prototype doesn't really say; an explicit requirement of _an average page load time of less than 1 second_ is quite explicit. Requirements can therefore be thought of more like an architect's blueprint: they provide explicit definitions and scaffolding of project success. Do you really have to plan by _writing down_ requirements? For example, why not do what designers do, expressing requirements in the form of prototypes and mockups. These _implicitly_ state requirements, because they suggest what the software is supposed to do without saying it directly. But for some types of requirements, they actually imply nothing. For example, how responsive should a web page be to be? A prototype doesn't really say; an explicit requirement of _an average page load time of less than 1 second_ is quite explicit. Requirements can therefore be thought of more like an architect's blueprint: they provide explicit definitions and scaffolding of project success.
And yet, like design, requirements come from the world and the people in it and not from software <jackson01>. Because they come from the world, requirements are rarely objective or unambiguous. For example, some requirements come from law, such as the European Union's General Data Protection Regulation [GDPR|https://eugdpr.org/] regulation, which specifies a set of data privacy requirements that all software systems used by EU citizens must meet. Other requirements might come from public pressure for change, as in Twitter's decision to label particular tweets as having false information or hate speech. Therefore, the methods that people use to do requirements engineering are quite diverse. Requirements engineers may work with lawyers to interpret policy. They might work with regulators to negotiate requirements. They might also use design methods, such as user research methods and rapid prototyping to iteratively converge toward requirements <lamsweerde08>. Therefore, the big difference between design and requirements engineering is that requirements engineers take the process one step further than designers, enumerating _in detail_ every property that the software must satisfy, and engaging with every source of requirements a system might need to meet, not just user needs. And yet, like design, requirements come from the world and the people in it and not from software<jackson01>. Because they come from the world, requirements are rarely objective or unambiguous. For example, some requirements come from law, such as the European Union's General Data Protection Regulation [GDPR|https://eugdpr.org/] regulation, which specifies a set of data privacy requirements that all software systems used by EU citizens must meet. Other requirements might come from public pressure for change, as in Twitter's decision to label particular tweets as having false information or hate speech. Therefore, the methods that people use to do requirements engineering are quite diverse. Requirements engineers may work with lawyers to interpret policy. They might work with regulators to negotiate requirements. They might also use design methods, such as user research methods and rapid prototyping to iteratively converge toward requirements<lamsweerde08>. Therefore, the big difference between design and requirements engineering is that requirements engineers take the process one step further than designers, enumerating _in detail_ every property that the software must satisfy, and engaging with every source of requirements a system might need to meet, not just user needs.
There are some approaches to specifying requirements _formally_. These techniques allow requirements engineers to automatically identify _conflicting_ requirements, so they don't end up proposing a design that can't possibly exist. Some even use systems to make requirements _traceable_, meaning the high level requirement can be linked directly to the code that meets that requirement <mader15>. All of this formality has tradeoffs: not only does it take more time to be so precise, but it can negatively effect creativity in concept generation as well <mohanani14>. There are some approaches to specifying requirements _formally_. These techniques allow requirements engineers to automatically identify _conflicting_ requirements, so they don't end up proposing a design that can't possibly exist. Some even use systems to make requirements _traceable_, meaning the high level requirement can be linked directly to the code that meets that requirement<mader15>. All of this formality has tradeoffs: not only does it take more time to be so precise, but it can negatively effect creativity in concept generation as well<mohanani14>.
Expressing requirements in natural language can mitigate these effects, at the expense of precision. They just have to be *complete*, *precise*, *non-conflicting*, and *verifiable*. For example, consider a design for a simple to do list application. It's requirements might be something like the following: Expressing requirements in natural language can mitigate these effects, at the expense of precision. They just have to be *complete*, *precise*, *non-conflicting*, and *verifiable*. For example, consider a design for a simple to do list application. It's requirements might be something like the following:
@ -38,4 +38,4 @@ Lastly, remember that requirements are translated from a design, and designs hav
No loan application with an applicant self-identified as Black should be approved. No loan application with an applicant self-identified as Black should be approved.
" "
That requirement is both precise and verifiable. In the 1980's, it was legal. But it was definitely not ethical or just? No. Therefore, requirements, no matter how formally extracted from a design specification, no matter how consistent with law, and no matter how aligned with an organization's priorities, is free from racist ideas. Requirements are just one of many ways that such ideas are manifested, and ultimately hidden in code <benjamin19>. That requirement is both precise and verifiable. In the 1980's, it was legal. But it was definitely not ethical or just? No. Therefore, requirements, no matter how formally extracted from a design specification, no matter how consistent with law, and no matter how aligned with an organization's priorities, is free from racist ideas. Requirements are just one of many ways that such ideas are manifested, and ultimately hidden in code<benjamin19>.

View file

@ -31,7 +31,7 @@ Because an `int` is well-defined in most languages, the two inputs to the functi
Of course, if the above was JavaScript code (which doesn't support static typing), JavaScript does nothing to actually verify that the data given to `min()` are actually integers. It's entirely fine with someone sending a string and an object. This probably won't do what you intended, leading to defects. Of course, if the above was JavaScript code (which doesn't support static typing), JavaScript does nothing to actually verify that the data given to `min()` are actually integers. It's entirely fine with someone sending a string and an object. This probably won't do what you intended, leading to defects.
This brings us to a second purpose of writing functional specifications: to help _verify_ that functions, their input, and their output are correct. Tests of functions and other low-level procedures are called *unit tests*. There are many ways to use specifications to verify correctness. By far, one of the simplest and most widely used kinds of unit tests are *assertions* <clarke06>. Assertions consist of two things: 1) a check on some property of a function's input or output and 2) some action to notify about violations of these properties. For example, if we wanted to verify that the JavaScript function above had integer values as inputs, we would do this: This brings us to a second purpose of writing functional specifications: to help _verify_ that functions, their input, and their output are correct. Tests of functions and other low-level procedures are called *unit tests*. There are many ways to use specifications to verify correctness. By far, one of the simplest and most widely used kinds of unit tests are *assertions*<clarke06>. Assertions consist of two things: 1) a check on some property of a function's input or output and 2) some action to notify about violations of these properties. For example, if we wanted to verify that the JavaScript function above had integer values as inputs, we would do this:
` `
// Return the smaller of the two numbers, or if they're equal, the second number. // Return the smaller of the two numbers, or if they're equal, the second number.
@ -44,11 +44,11 @@ function min(a, b) {
} }
` `
These two new lines of code are essentially functional specifications that declare "_If either of those inputs is not an integer, the caller of this function is doing something wrong_". This is useful to declare, but assertions have a bunch of problems: if your program _can_ send a non-integer value to min, but you never test it in a way that does, you'll never see those alerts. This form of *dynamic verification* is therefore very limited, since it provides weaker guarantees about correctness. That said, a study of the use of assertions in a large database of GitHub projects shows that use of assertions _is_ related to fewer defects <casalnuovo15-2> (though note that I said "related": we have no evidence that assertions actually prevent defects. It may be possible that developers who use assertions are just better at avoiding defects.) These two new lines of code are essentially functional specifications that declare "_If either of those inputs is not an integer, the caller of this function is doing something wrong_". This is useful to declare, but assertions have a bunch of problems: if your program _can_ send a non-integer value to min, but you never test it in a way that does, you'll never see those alerts. This form of *dynamic verification* is therefore very limited, since it provides weaker guarantees about correctness. That said, a study of the use of assertions in a large database of GitHub projects shows that use of assertions _is_ related to fewer defects<casalnuovo15-2> (though note that I said "related": we have no evidence that assertions actually prevent defects. It may be possible that developers who use assertions are just better at avoiding defects.)
Assertions are related to the broader category of *error handling* language features. Error handling includes assertions, but also programming language features like exceptions and exception handlers. Error handling is a form of specification in that _checking_ for errors usually entails explicitly specifying the conditions that determine an error. For example, in the code above, the condition `Number.isInteger(a)` specifies that the parameter `a` must be an integer. Other exception handling code such as the Java `throws` statement indicates the cases in which errors can occur and the corresponding `catch` statement indicates what is to done about errors. It is difficult to implement good exception handling that provides granular, clear ways of recovering from errors <chen09>. Evidence shows that modern developers are still exceptionally bad at designing for errors; one study found that errors are not designed for, few errors are tested for, and exception handling is often overly general, providing little ability for users to understand errors or for developers to debug them <ebert15>. These difficulties appear to be because it is difficult to imagine the vast range of errors that can occur <maxion00>. Assertions are related to the broader category of *error handling* language features. Error handling includes assertions, but also programming language features like exceptions and exception handlers. Error handling is a form of specification in that _checking_ for errors usually entails explicitly specifying the conditions that determine an error. For example, in the code above, the condition `Number.isInteger(a)` specifies that the parameter `a` must be an integer. Other exception handling code such as the Java `throws` statement indicates the cases in which errors can occur and the corresponding `catch` statement indicates what is to done about errors. It is difficult to implement good exception handling that provides granular, clear ways of recovering from errors<chen09>. Evidence shows that modern developers are still exceptionally bad at designing for errors; one study found that errors are not designed for, few errors are tested for, and exception handling is often overly general, providing little ability for users to understand errors or for developers to debug them<ebert15>. These difficulties appear to be because it is difficult to imagine the vast range of errors that can occur<maxion00>.
Researchers have invented many forms of specification that require more work and more thought to write, but can be used to make stronger guarantees about correctness <woodcock09>. For example, many languages support the expression of formal *pre-conditions* and *post-conditions* that represent contracts that must be kept for the program to be corect. (*Formal* means mathematical, facilitating mathematical proofs that these conditions are met). Because these contracts are essentially mathematical promises, we can build tools that automatically read a function's code and verify that what it computes exhibits those mathematical properties using automated theorem proving systems. For example, suppose we wrote some formal specifications for our example above to replace our assertions (using a fictional notation for illustration purposes): Researchers have invented many forms of specification that require more work and more thought to write, but can be used to make stronger guarantees about correctness<woodcock09>. For example, many languages support the expression of formal *pre-conditions* and *post-conditions* that represent contracts that must be kept for the program to be corect. (*Formal* means mathematical, facilitating mathematical proofs that these conditions are met). Because these contracts are essentially mathematical promises, we can build tools that automatically read a function's code and verify that what it computes exhibits those mathematical properties using automated theorem proving systems. For example, suppose we wrote some formal specifications for our example above to replace our assertions (using a fictional notation for illustration purposes):
` `
// pre-conditions: a in Integers, b in Integers // pre-conditions: a in Integers, b in Integers
@ -60,6 +60,6 @@ function min(a, b) {
The annotations above require that, no matter what, the inputs have to be integers and the output has to be less than or equal to both values. The automatic theorem prover can then start with the claim that result is always less than or equal to both and begin searching for a counterexample. Can you find a counterexample? Really try. Think about what you're doing while you try: you're probably experimenting with different inputs to identify arguments that violate the contract. That's similar to what automatic theorem provers do, but they use many tricks to explore large possible spaces of inputs all at once, and they do it very quickly. The annotations above require that, no matter what, the inputs have to be integers and the output has to be less than or equal to both values. The automatic theorem prover can then start with the claim that result is always less than or equal to both and begin searching for a counterexample. Can you find a counterexample? Really try. Think about what you're doing while you try: you're probably experimenting with different inputs to identify arguments that violate the contract. That's similar to what automatic theorem provers do, but they use many tricks to explore large possible spaces of inputs all at once, and they do it very quickly.
There are definite tradeoffs with writing detailed, formal specifications. The benefits are clear: many companies have written formal functional specifications in order to make _completely_ unambiguous the required behavior of their code, particularly systems that are capable of killing people or losing money, such as flight automation software, banking systems, and even compilers that create executables from code <woodcock09>. In these settings, it's worth the effort of being 100% certain that the program is correct because if it's not, people can die. Specifications can have other benefits. The very act of writing down what you expect a function to do in the form of test cases can slow developers down, causing to reflect more carefully and systematically about exactly what they expect a function to do <fucci16>. Perhaps if this is true in general, there's value in simply stepping back before you write a function, mapping out pre-conditions and post-conditions in the form of simple natural language comments, and _then_ writing the function to match your intentions. There are definite tradeoffs with writing detailed, formal specifications. The benefits are clear: many companies have written formal functional specifications in order to make _completely_ unambiguous the required behavior of their code, particularly systems that are capable of killing people or losing money, such as flight automation software, banking systems, and even compilers that create executables from code<woodcock09>. In these settings, it's worth the effort of being 100% certain that the program is correct because if it's not, people can die. Specifications can have other benefits. The very act of writing down what you expect a function to do in the form of test cases can slow developers down, causing to reflect more carefully and systematically about exactly what they expect a function to do<fucci16>. Perhaps if this is true in general, there's value in simply stepping back before you write a function, mapping out pre-conditions and post-conditions in the form of simple natural language comments, and _then_ writing the function to match your intentions.
Writing formal specifications can also have downsides. When the consequences of software failure aren't so high, the difficulty and time required to write and maintain functional specifications may not be worth the effort <petre13>. These barriers deter many developers from writing them <schiller14>. Formal specifications can also warp the types of data that developers work with. For example, it is much easier to write formal specifications about Boolean values and integers than string values. This can lead engineers to be overly reductive in how they model data (e.g., settling for binary models of gender, then gender is inherently non-binary and multidimensional). Writing formal specifications can also have downsides. When the consequences of software failure aren't so high, the difficulty and time required to write and maintain functional specifications may not be worth the effort<petre13>. These barriers deter many developers from writing them<schiller14>. Formal specifications can also warp the types of data that developers work with. For example, it is much easier to write formal specifications about Boolean values and integers than string values. This can lead engineers to be overly reductive in how they model data (e.g., settling for binary models of gender, then gender is inherently non-binary and multidimensional).

View file

@ -19,7 +19,7 @@ _"Well, it's worked this way for a long time, and people have built up a lot of
# Testing # Testing
So how do you _find_ defects in a program? Let's start with testing. Testing is generally the easiest kind of verification to do, but as a practice, it has questionable efficacy. Empirical studies of testing find that it _is_ related to fewer defects in the future, but not strongly related, and it's entirely possible that it's not the testing itself that results in fewer defects, but that other activities (such as more careful implementation) result in fewer defects and testing efforts <ahmed16>. At the same time, modern developers don't test as much as they think they do <beller15>. Moreover, students are often not convinced of the return on investment of automated tests and often opt for laborious manual tests (even though they regret it later) <pham14>. Testing is therefore in a strange place: it's a widespread activity in industry, but it's often not executed systematically, and there is some evidence that it doesn't seem to help prevent defects from being released. So how do you _find_ defects in a program? Let's start with testing. Testing is generally the easiest kind of verification to do, but as a practice, it has questionable efficacy. Empirical studies of testing find that it _is_ related to fewer defects in the future, but not strongly related, and it's entirely possible that it's not the testing itself that results in fewer defects, but that other activities (such as more careful implementation) result in fewer defects and testing efforts<ahmed16>. At the same time, modern developers don't test as much as they think they do<beller15>. Moreover, students are often not convinced of the return on investment of automated tests and often opt for laborious manual tests (even though they regret it later)<pham14>. Testing is therefore in a strange place: it's a widespread activity in industry, but it's often not executed systematically, and there is some evidence that it doesn't seem to help prevent defects from being released.
Why is this? One possibility is that *no amount of testing can prove a program correct with respect to its specifications*. Why? It boils down to the same limitations that exist in science: with empiricism, we can provide evidence that a program _does_ have defects, but we can't provide complete evidence that a program _doesn't_ have defects. This is because even simple programs can execute in a infinite number of different ways. Why is this? One possibility is that *no amount of testing can prove a program correct with respect to its specifications*. Why? It boils down to the same limitations that exist in science: with empiricism, we can provide evidence that a program _does_ have defects, but we can't provide complete evidence that a program _doesn't_ have defects. This is because even simple programs can execute in a infinite number of different ways.
@ -55,9 +55,9 @@ The above is basically an informal proof. I used logic to divide the possible st
The benefits of analysis is that it _can_ demonstrate that a program is correct in all cases. This is because they can handle infinite spaces of possible inputs by mapping those infinite inputs onto a finite space of possible executions. It's not always possible to do this in practice, since many kinds of programs _can_ execute in infinite ways, but it gets us closer to proving correctness. The benefits of analysis is that it _can_ demonstrate that a program is correct in all cases. This is because they can handle infinite spaces of possible inputs by mapping those infinite inputs onto a finite space of possible executions. It's not always possible to do this in practice, since many kinds of programs _can_ execute in infinite ways, but it gets us closer to proving correctness.
One popular type of automatic program analysis tools is a *static analysis* tool. These tools read programs and identify potential defects using the types of formal proofs like the ones above. They typically result in a set of warnings, each one requiring inspection by a developer to verify, since some of the warnings may be false positives (something the tool thought was a defect, but wasn't). Although static analysis tools can find many kinds of defects, they aren't yet viewed by developers to be that useful because the false positives are often large in number and the way they are presented make them difficult to understand <johnson13>. There is one exception to this, and it's a static analysis tool you've likely used: a compiler. Compilers verify the correctness of syntax, grammar, and for statically-typed languages, the correctness of types. As I'm sure you've discovered, compiler errors aren't always the easiest to comprehend, but they do find real defects automatically. The research community is just searching for more advanced ways to check more advanced specifications of program behavior. One popular type of automatic program analysis tools is a *static analysis* tool. These tools read programs and identify potential defects using the types of formal proofs like the ones above. They typically result in a set of warnings, each one requiring inspection by a developer to verify, since some of the warnings may be false positives (something the tool thought was a defect, but wasn't). Although static analysis tools can find many kinds of defects, they aren't yet viewed by developers to be that useful because the false positives are often large in number and the way they are presented make them difficult to understand<johnson13>. There is one exception to this, and it's a static analysis tool you've likely used: a compiler. Compilers verify the correctness of syntax, grammar, and for statically-typed languages, the correctness of types. As I'm sure you've discovered, compiler errors aren't always the easiest to comprehend, but they do find real defects automatically. The research community is just searching for more advanced ways to check more advanced specifications of program behavior.
Not all analytical techniques rely entirely on logic. In fact, one of the most popular methods of verification in industry are *code reviews*, also known as _inspections_. The basic idea of an inspection is to read the program analytically, following the control and data flow inside the code to look for defects. This can be done alone, in groups, and even included as part of process of integrating changes, to verify them before they are committed to a branch. Modern code reviews, while informal, help find defects, stimulate knowledge transfer between developers, increase team awareness, and help identify alternative implementations that can improve quality <bacchelli13>. One study found that measures of how much a developer knows about an architecture can increase 66% to 150% depending on the project <rigby13>. That said, not all reviews are created equal: the best ones are thorough and conducted by a reviewer with strong familiarity with the code <kononenko16>; including reviewers that do not know each other or do not know the code can result in longer reviews, especially when run as meetings <seaman97>. Soliciting reviews asynchronously by allowing developers to request reviewers of their peers is generally much more scalable <rigby11>, but this requires developers to be careful about which reviews they invest in. These choices about where to put reviewing attention can result in great disparities in what is reviewed, especially in open source: the more work a review is perceived to be, the less likely it is to be reviewed at all and the longer the delays in receiving a review <thongtanunam16>. Not all analytical techniques rely entirely on logic. In fact, one of the most popular methods of verification in industry are *code reviews*, also known as _inspections_. The basic idea of an inspection is to read the program analytically, following the control and data flow inside the code to look for defects. This can be done alone, in groups, and even included as part of process of integrating changes, to verify them before they are committed to a branch. Modern code reviews, while informal, help find defects, stimulate knowledge transfer between developers, increase team awareness, and help identify alternative implementations that can improve quality<bacchelli13>. One study found that measures of how much a developer knows about an architecture can increase 66% to 150% depending on the project<rigby13>. That said, not all reviews are created equal: the best ones are thorough and conducted by a reviewer with strong familiarity with the code<kononenko16>; including reviewers that do not know each other or do not know the code can result in longer reviews, especially when run as meetings<seaman97>. Soliciting reviews asynchronously by allowing developers to request reviewers of their peers is generally much more scalable<rigby11>, but this requires developers to be careful about which reviews they invest in. These choices about where to put reviewing attention can result in great disparities in what is reviewed, especially in open source: the more work a review is perceived to be, the less likely it is to be reviewed at all and the longer the delays in receiving a review<thongtanunam16>.
Beyond these more technical considerations around verifying a program's correctness are organizational issues around different software qualities. For example, different organizations have different sensitivities to defects. If a $0.99 game on the app store has a defect, that might not hurt its sales much, unless that defect prevents a player from completing the game. If Boeing's flight automation software has a defect, hundreds of people might die. The game developer might do a little manual play testing, release, and see if anyone reports a defect. Boeing will spend years proving mathematically with automatic program analysis that every line of code does what is intended, and repeating this verification every time a line of code changes. Moreover, requirements may change differently in different domains. For example, a game company might finally recognize the sexist stereotypes amplified in its game mechanics and have to change requirements, resulting in changed definitions of correctness, and the incorporation of new software qualities such as bias into testing plans. Similarly, Boeing might have to respond to pandemic fears by having to shift resources away from verifying flight crash safety to verifying public health safety. What type of verification is right for your team depends entirely on what a team is building, who's using it, and how they're depending on it. Beyond these more technical considerations around verifying a program's correctness are organizational issues around different software qualities. For example, different organizations have different sensitivities to defects. If a $0.99 game on the app store has a defect, that might not hurt its sales much, unless that defect prevents a player from completing the game. If Boeing's flight automation software has a defect, hundreds of people might die. The game developer might do a little manual play testing, release, and see if anyone reports a defect. Boeing will spend years proving mathematically with automatic program analysis that every line of code does what is intended, and repeating this verification every time a line of code changes. Moreover, requirements may change differently in different domains. For example, a game company might finally recognize the sexist stereotypes amplified in its game mechanics and have to change requirements, resulting in changed definitions of correctness, and the incorporation of new software qualities such as bias into testing plans. Similarly, Boeing might have to respond to pandemic fears by having to shift resources away from verifying flight crash safety to verifying public health safety. What type of verification is right for your team depends entirely on what a team is building, who's using it, and how they're depending on it.