What Can Journalists Teach Developers About Writing Source Code?
Alex Aitken @alexaitken
Alistair McKinnell @amckinnell
In some sense techniques for controllingcomplexity is what computer science is about.
Harold Abelson
Agenda
What Is Inverted Pyramid ? !
Writing Source Code Using Inverted Pyramid
What Is Inverted Pyramid ?
The inverted pyramid is a metaphor used by journalists to illustrate how information should be prioritized.
The Lead
“The Lead”
“The Body”
“The Tail”
“The Lead”: the most important information
The Lead
“The Lead”
“The Body”
“The Tail”
The Lead
“The Lead”
“The Body”
“The Tail”
“The Lead”: the most important information
“The Body”: the crucial information
The Lead
“The Lead”
“The Body”
“The Tail”
“The Lead”: the most important information
“The Body”: the crucial information
“The Tail”: any extra information
The Lead
1. Readers can leave an article at any point and understand it, even if they do not have all the details.
2. For those readers who wish to proceed, it conducts them through the article details.
Benefits for Readers
Exercise
• Arrange cards as above • 1–2 groups per table • 3–4 people per group
1 2222
• Experience inverted pyramid • Read one card at a time • You have 4 minutes • It’s not important to read all cards
Some Boos at Graduation After Judge Bars PrayerAssociated PressMay 21, 2001
WASHINGTON, Ill. - A top student who gave a traditional farewell speech at a high school graduation was booed and another student was applauded for holding a moment of silence after a judge barred prayer at the ceremony. 2222
The Lead
“The Lead”
“The Body”
“The Tail”
Some Boos at Graduation After Judge Bars Prayer Associated PressMay 21, 2001
WASHINGTON, Ill. - A top student who gave a traditional farewell speech at a high school graduation was booed and another student was applauded for holding a moment of silence after a judge barred prayer at the ceremony.
A federal judge issued a restraining order days before Sunday's ceremony at Washington Community High School blocking any student-led prayer. It was the first time in the 80-year history of the school that no graduation prayers were said.
Natasha Appenheimer, the class valedictorian, traditionally a top student chosen to give the class graduation speech, was booed when she received her diploma. Her family, backed by the American Civil Liberties Union, had filed the lawsuit that led to the restraining order. Meanwhile, some stood and applauded class speaker Ryan Brown when he bowed his head for a moment of silence before his speech.
About 200 people attended a prayer vigil before the ceremony, and a placard-carrying atheist and a Pentecostal minister got into a shouting match.
In spite of the turbulent atmosphere, Appenheimer said she wasn't upset by the way things turned out.
"It's my graduation. I'm happy," she said. The lawsuit "was worth it. We changed things, we righted a wrong and made something better than it was before. I learned that when you believe in something, you should stand up for it."
Graduate Annie White disagreed, saying many class members wanted to demonstrate that "God was a part of our graduation."
Superintendent Lee Edwards said the school district might appeal McDade's ruling. He said the invocation and benediction prayers usually said at the ceremony were innocuous, and "You would have to have been working pretty hard to be offended."
School district officials defended the prayer on grounds that students, not administrators, were in charge of graduation.
The Supreme Court's landmark 1962 decision outlawed organized prayer in public schools. In 1992, the justices barred clergy-led prayers at graduations, and last year, the court barred officials from letting students lead crowds in prayer before football games.
public class StringCalculator {! public int add(String numbers) { List<Integer> values = convertToInteger(parseValues(numbers));! failWhenContainsNegatives(values);! return sumOf(values); }! private String[] parseValues(String numbers) { if (numbers.isEmpty()) { return new String[] {}; }! if (containsCustomDelimiters(numbers)) { return parseCustomDelimitedValues(numbers); }! return parseStandardDelimiters(numbers); }! private List<Integer> convertToInteger(String[] numbers) { List<Integer> result = new ArrayList<>();! for (String number : numbers) { result.add(toInteger(number)); }! return result; }! private void failWhenContainsNegatives(List<Integer> values) { List<Integer> negatives = new ArrayList<>();! for (Integer value : values) { if (value < 0) negatives.add(value); }! if (!negatives.isEmpty()) { String message = "Error: negatives not allowed " + negatives; throw new RuntimeException(message); } }
private int sumOf(List<Integer> values) { int result = 0;! for (Integer value : values) { result += value; }! return result; }! private boolean containsCustomDelimiters(String numbers) { return numbers.startsWith("//"); }! private String[] parseCustomDelimitedValues(String numbers) { int newlineIndex = numbers.indexOf('\n');! String delimiters = numbers.substring(2, newlineIndex); String numberList = numbers.substring(newlineIndex + 1); String[] customDelimiters = parseCustomDelimiters(delimiters);! for (String customDelimiter : customDelimiters) { numberList = numberList.replaceAll( quoteRegularExpression(customDelimiter), ","); }! return numberList.split(","); }! private String[] parseCustomDelimiters(String rawCustomDelimiters) { return rawCustomDelimiters.replaceAll("\\[", "").split("\\]"); }! private String quoteRegularExpression(String customSeparator) { return "\\Q" + customSeparator + "\\E"; }! private String[] parseStandardDelimiters(String numbers) { return numbers.split("[,\n]"); }! private Integer toInteger(String number) { Integer value = Integer.parseInt(number);! return value <= 1000 ? value : 0; }!}
public class StringCalculator {! public int add(String numbers) { List<Integer> values = convertToInteger(parseValues(numbers));! failWhenContainsNegatives(values);! return sumOf(values); }! private String[] parseValues(String numbers) { if (numbers.isEmpty()) { return }! if (containsCustomDelimiters(numbers)) { return }! return parseStandardDelimiters(numbers); }! private List<Integer> convertToInteger(String[] numbers) { List<Integer> result = ! for (String number : numbers) { result.add(toInteger(number)); }! return result; }! private void failWhenContainsNegatives(List<Integer> values) { List<Integer> negatives = ! for (Integer value : values) { if (value < 0) negatives.add(value); }! if (!negatives.isEmpty()) { String message = throw } }
private int int! for result += value; }! return }! private return }! private int! String delimiters = numbers.substring(2, newlineIndex); String numberList = numbers.substring(newlineIndex + 1); String[] customDelimiters = parseCustomDelimiters(delimiters);! for numberList = numberList.replaceAll( quoteRegularExpression(customDelimiter), }! return }! private return }! private return }! private return }! private Integer value = Integer.parseInt(number);! return }!}
1. Readers can leave an article at any point and understand it, even if they do not have all the details.
2. For those readers who wish to proceed, it conducts them through the article details.
Benefits for ReadersThe Lead
1. Readers can leave an article at any point and understand it, even if they do not have all the details.
2. For those readers who wish to proceed, it conducts them through the article details.
Benefits for DevelopersThe Lead
1.
2. For those readers who wish to proceed, it conducts them through the article details.
Benefits for Developers
1. Developers can leave source code at any point and understand it, even if they do not have all the details.
The Lead
1. Developers can leave source code at any point and understand it, even if they do not have all the details.
2. For those developers who need to see more implementation details, it conducts them through the source code.
Benefits for DevelopersThe Lead
1. Understanding without all the details
2. Details are effectively organized
Benefits for DevelopersThe Lead
Exercise
• Same groups as last exercise • Arrange cards as above
TailTailTailBodyBodyBodyLead
numbers add(numbers)
“1,2,3” 6
“1,1001,2” 3
“1,-2,3,-4” Error showing -2, -4
“//#\n2#4#6” 12
int add(String numbers)
numbers add(numbers)
“1,2,3” 6
“1,1001,2” 3
“1,-2,3,-4” Error showing -2, -4
“//#\n2#4#6” 12
int add(String numbers)
“//#\n2#4#6”
• Flip over the card labelled Lead • What can you say about the add()
method from reading the Lead card ? • Share that understanding with your group
public int add(String numbers) { List<Integer> values = convertToInteger(parseValues(numbers));
failWhenContainsNegatives(values);
return sumOf(values);}
TailTailTail
BodyBodyBodyBody
BodyBodyBody
• Flip over the Body cards one at a time • Ask yourself: What do I notice about the
code after reading each card ? • Next, flip over the Tail cards • It’s not important to read all cards
public int add(String numbers) { List<Integer> values = convertToInteger(parseValues(numbers));
failWhenContainsNegatives(values);
return sumOf(values);}
TailTailTailBodyBodyprivate String[] parseValues(String numbers) { if (numbers.isEmpty()) { return new String[] {}; }
if (containsCustomDelimiters(numbers)) { return parseCustomDelimitedValues(numbers); }
return parseStandardDelimiters(numbers);}
public class StringCalculator {! public int add(String numbers) { List<Integer> values = convertToInteger(parseValues(numbers));! failWhenContainsNegatives(values);! return sumOf(values); }! private String[] parseValues(String numbers) { if (numbers.isEmpty()) { return new String[] {}; }! if (containsCustomDelimiters(numbers)) { return parseCustomDelimitedValues(numbers); }! return parseStandardDelimiters(numbers); }! private List<Integer> convertToInteger(String[] numbers) { List<Integer> result = new ArrayList<>();! for (String number : numbers) { result.add(toInteger(number)); }! return result; }! private void failWhenContainsNegatives(List<Integer> values) { List<Integer> negatives = new ArrayList<>();! for (Integer value : values) { if (value < 0) negatives.add(value); }! if (!negatives.isEmpty()) { String message = "Error: negatives not allowed " + negatives; throw new RuntimeException(message); } }
private int sumOf(List<Integer> values) { int result = 0;! for (Integer value : values) { result += value; }! return result; }! private boolean containsCustomDelimiters(String numbers) { return numbers.startsWith("//"); }! private String[] parseCustomDelimitedValues(String numbers) { int newlineIndex = numbers.indexOf('\n');! String delimiters = numbers.substring(2, newlineIndex); String numberList = numbers.substring(newlineIndex + 1); String[] customDelimiters = parseCustomDelimiters(delimiters);! for (String customDelimiter : customDelimiters) { numberList = numberList.replaceAll( quoteRegularExpression(customDelimiter), ","); }! return numberList.split(","); }! private String[] parseCustomDelimiters(String rawCustomDelimiters) { return rawCustomDelimiters.replaceAll("\\[", "").split("\\]"); }! private String quoteRegularExpression(String customSeparator) { return "\\Q" + customSeparator + "\\E"; }! private String[] parseStandardDelimiters(String numbers) { return numbers.split("[,\n]"); }! private Integer toInteger(String number) { Integer value = Integer.parseInt(number);! return value <= 1000 ? value : 0; }!}
public class StringCalculator {! public int add(String numbers) { List<Integer> values = convertToInteger(parseValues(numbers));! failWhenContainsNegatives(values);! return sumOf(values); }
public class StringCalculator {! public int add(String numbers) { List<Integer> values = convertToInteger(parseValues(numbers));! failWhenContainsNegatives(values);! return sumOf(values); }
The Lead
Understanding without all the detail
Details are effectively organized
Writing Source Code Using Inverted Pyramid
Compose Method !
You can’t rapidly understand a method’s logic.
!
Transform the logic into a small number of intention-revealing steps at the same
level of detail.
Composed Method !
Compose methods out of calls to other methods, each
of which is at roughly the same level of abstraction.
Composed Method and SLAP !
Composed method encourages factoring (or
refactoring) code into small, cohesive, readable chunks.
SLAP stands for the Single
Level of Abstraction Principle.
Example
public int add(String numbers) { List<Integer> negatives = new ArrayList<>(); int sum = 0; for (String number : parseValues(numbers)) { Integer value = Integer.parseInt(number); if (value < 0) { negatives.add(value); } else if (value <= 1000) { sum += value; } }
if (!negatives.isEmpty()) { throw new RuntimeException("Error: negatives not allowed " + negatives); }
return sum;}
You can’t rapidly understanda method’s logic.
public int add(String numbers) { List<Integer> negatives = new ArrayList<>(); int sum = 0; for (String number : parseValues(numbers)) { Integer value = Integer.parseInt(number); if (value < 0) { negatives.add(value); } else if (value <= 1000) { sum += value; } }
if (!negatives.isEmpty()) { throw new RuntimeException("Error: negatives not allowed " + negatives); }
return sum;}
Compose methods out of calls to other methods, each
of which is at roughly the same level of abstraction.
public int add(String numbers) { List<Integer> values = convertToInteger(parseValues(numbers));! failWhenContainsNegatives(values);! return sumOf(values);}
public int add(String numbers) { List<Integer> values = convertToInteger(parseValues(numbers));! failWhenContainsNegatives(values);! return sumOf(values);}
Understanding without all the detail
Details are effectively organized
The Lead
Example
The Lead
The Lead
The Lead
private String[] parseValues(String numbers) { if (numbers.isEmpty()) return new String[] {};! if (numbers.startsWith("//")) { int newlineIndex = numbers.indexOf('\n'); String delimiters = numbers.substring(2, newlineIndex); String numberList = numbers.substring(newlineIndex + 1); String[] customDelimiters = delimiters. replaceAll("\\[", "").split("\\]"); for (String customDelimiter : customDelimiters) { numberList = numberList. replaceAll("\\Q" + customDelimiter + "\\E", ","); } return numberList.split(","); }! return numbers.split("[,\n]");}
private String[] parseValues(String numbers) { if (numbers.isEmpty()) return new String[] {};! if (numbers.startsWith("//")) { int newlineIndex = numbers.indexOf('\n'); String delimiters = numbers.substring(2, newlineIndex); String numberList = numbers.substring(newlineIndex + 1); String[] customDelimiters = delimiters. replaceAll("\\[", "").split("\\]"); for (String customDelimiter : customDelimiters) { numberList = numberList. replaceAll("\\Q" + customDelimiter + "\\E", ","); } return numberList.split(","); }! return numbers.split("[,\n]");}
private String[] parseValues(String numbers) { if (numbers.isEmpty()) return new String[] {};! if (numbers.startsWith("//")) { return parseCustomDelimitedValues(numbers); }! return numbers.split("[,\n]");}
private String[] parseValues(String numbers) { if (numbers.isEmpty()) return new String[] {};! if (numbers.startsWith("//")) { return parseCustomDelimitedValues(numbers); }! return numbers.split("[,\n]");}
SLAP stands for the Single Level of Abstraction Principle.
private String[] parseValues(String numbers) { if (numbers.isEmpty()) return new String[] {};! if (numbers.startsWith("//")) { return parseCustomDelimitedValues(numbers); }! return numbers.split("[,\n]");}
SLAP stands for the Single Level of Abstraction Principle.
private String[] parseValues(String numbers) { if (numbers.isEmpty()) return new String[] {};! if (numbers.startsWith("//")) { return parseCustomDelimitedValues(numbers); }! return numbers.split("[,\n]");}
private String[] parseValues(String numbers) { if (numbers.isEmpty()) return new String[] {};! if (containsCustomDelimiters(numbers)) { return parseCustomDelimitedValues(numbers); }! return numbers.split("[,\n]");}
private String[] parseValues(String numbers) { if (numbers.isEmpty()) return new String[] {};! if (containsCustomDelimiters(numbers)) { return parseCustomDelimitedValues(numbers); }! return parseStandardDelimiters(numbers);}
private String[] parseValues(String numbers) { if (numbers.isEmpty()) return new String[] {};! if (containsCustomDelimiters(numbers)) { return parseCustomDelimitedValues(numbers); }! return parseStandardDelimiters(numbers);}
SLAP stands for the Single Level of Abstraction Principle.
private String[] parseValues(String numbers) { if (numbers.isEmpty()) return new String[] {};! if (containsCustomDelimiters(numbers)) { return parseCustomDelimitedValue(numbers); }! return parseStandardDelimiters(numbers);}
Understanding without all the detail
Details are effectively organized
The Lead
Applying Inverted Pyramid
The Lead
The Lead
•Can lead to an overabundance of small methods.
•Can make debugging difficult because logic is spread out across many small methods.
Compose Method
Refactoring to Patterns
Liabilities
• Efficiently communicates what a method does and how it does what it does.
• Simplifies a method by breaking it up into well-named chunks of behaviour at the same level of detail.
Compose Method
Refactoring to Patterns
Benefits
The Lead
“The Lead”
“The Body”
“The Tail”
1. Developers can leave source code at any point and understand it, even if they do not have all the details.
2. For those developers who need to see more implementation details, it conducts them through the source code.
Benefits for DevelopersThe Lead
Exercise
• Grab an index card to write on • Write down 2 things you are going to
consider next time you write code
1
• Take turns sharing what you wrote with everyone at your table
• This is optional. Unlike in kindergarten, you don’t have to share
1
In some sense techniques for controllingcomplexity is what computer science is about.
Harold Abelson
1. Understanding without all the details
2. Details are effectively organized
Benefits for DevelopersThe Lead
Ward Cunningham
You know you are working on clean code when each routine you read turns out to be
pretty much what you expected.
Refactoring to Patterns Joshua Kerievsky
Implementation Patterns Kent Beck
The Productive Programmer Neal Ford
Clean Code Robert C. Martin
Book
s
Phot
os www.flickr.com/photos/ecu_digital_collections/3288382289/
www.morguefile.com/archive/display/848917
Top Related