You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+78-40Lines changed: 78 additions & 40 deletions
Original file line number
Diff line number
Diff line change
@@ -2,19 +2,32 @@
2
2
3
3
## Overview
4
4
5
-
This repo contains code samples demonstrating how to transform a complex, linear script into a modular, easier-to-maintain package. The code is written as a reference for the *Python: Beyond the Basics* class at [NICAR 2014](http://ire.org/conferences/nicar-2014/), but can also work as a stand-alone tutorial.
5
+
This repo contains code samples demonstrating how to transform a complex, linear script into a modular,
6
+
easier-to-maintain package. The code is written as a reference for the *Python: Beyond the Basics* class
7
+
at [NICAR 2014][], but can also work as a stand-alone tutorial.
6
8
7
-
The tutorial uses a small, [fake set of election results](https://docs.google.com/spreadsheet/pub?key=0AhhC0IWaObRqdGFkUW1kUmp2ZlZjUjdTYV9lNFJ5RHc&output=csv) created for demonstration purposes.
9
+
The tutorial uses a small, [fake set of election results][] for demonstration purposes.
8
10
9
-
Project code evolves through four phases, each contained in a numbered *elex* directory. Below are descriptions of each phase, along with related questions and exercises that often anticipate the next phase or set of skills.
11
+
Project code evolves through four phases, each contained in a numbered *elex* directory. Below are descriptions of each phase,
12
+
along with related questions and exercises that often anticipate the next phase or set of skills.
10
13
11
-
The goal is to demonstrate how to use Python functions, modules, packages and classes to organize code more effectively. We also introduce unit testing as a strategy for writing programs that you can update with confidence. The overarching theme: **_As an application or program grows in size, writing readable code with tests can help tame complexity and keep you sane._**
14
+
The goal is to demonstrate how to use Python functions, modules, packages and classes to organize code more effectively.
15
+
We also introduce unit testing as a strategy for writing programs that you can update with confidence. The overarching theme:
16
+
**_As an application or program grows in size, writing readable code with tests can help tame complexity and keep you sane._**
12
17
13
-
Wondering how to use this tutorial or why the hell we called it *refactoring101*? The [FAQ](https://github.com/PythonJournos/refactoring101/wiki/FAQ) has answers to these and sundry other questions. Also, check out the [Resources](https://github.com/PythonJournos/refactoring101/wiki/Resources) page for wisdom from our tribal elders.
18
+
Wondering how to use this tutorial or why the hell we called it *refactoring101*? The [FAQ][] has answers to
19
+
these and sundry other questions. Also, check out the [Resources][] page for wisdom from our tribal elders.
We begin with a single, linear script in the _elex1/_ directory. Below are a few reasons why this [code smells](http://en.wikipedia.org/wiki/Code_smell) (some might even say it reeks):
28
+
We begin with a single, linear script in the _elex1/_ directory. Below are a few reasons why this [code smells][] (some might even say it reeks):
* It's hard to understand. You have to read the entire script before getting a full sense of what it does.
20
33
* It's hard to debug when something goes wrong.
@@ -29,7 +42,7 @@ We begin with a single, linear script in the _elex1/_ directory. Below are a few
29
42
30
43
#### Exercises
31
44
32
-
*Try slicing up this code into a bunch of functions, where related bits of logic are grouped together.
45
+
*Slice up this code into a bunch of functions, where related bits of logic are grouped together.
33
46
* Write a unit test for one or more functions extracted from this module.
34
47
35
48
@@ -49,7 +62,10 @@ could be a lot better.
49
62
#### Exercises
50
63
51
64
* List three ways this code is better than the previous version; and three ways it could be improved.
52
-
* Organize functions in election_results.py into two or more new modules. (Hint: There is no right answer here. [Naming things is hard](http://martinfowler.com/bliki/TwoHardThings.html); aim for directory and file names that are short but meaningful to a normal human).
65
+
* Organize functions in election_results.py into two or more new modules. (Hint: There is no right answer here.
66
+
[Naming things is hard][]; aim for directory and file names that are short but meaningful to a normal human).
67
+
68
+
[Naming things is hard]: http://martinfowler.com/bliki/TwoHardThings.html
53
69
54
70
## Phase 3 - Modularize
55
71
@@ -81,7 +97,7 @@ Note that we did not change any of our functions. Mostly we just re-organized th
81
97
with the goal of grouping related bits of logic in common-sense locations. We also migrated
82
98
imports and "namespaced" imports of our own re-usable code under _elex3.lib_.
83
99
84
-
**Important**: You must add the _refactoring101_ directory to your _PYTHONPATH_ before any of the tests or script will work.
100
+
> **Note**: You must add the _refactoring101_ directory to your _PYTHONPATH_ before any of the tests or script will work.
Let's apply a similar process for the party transformation.
170
200
171
201
#### Add party
172
202
203
+
The candidate party requires special handling for Democratics and Republicans. Otherwise we'll default to the raw party value.
204
+
173
205
* Migrate party-related tests from *tests/test_parser.py* to *TestCandidate* in *tests/test_models.py*.
174
206
175
207
> **Note**: Don't forget to add the party argument to the Candidate instance in *test_candidate_name*.
@@ -193,56 +225,56 @@ Let's apply a similar process for the party transformation.
193
225
194
226
##### Observations
195
227
196
-
In the party refactoring above, notice that we're not directly testing the *clean_party* method but simply checking for the correct value of the *party* attribute on candidate instances. The *clean_party* code has been nicely
228
+
In the party refactoring above, notice that we're not directly testing the *clean_party* method but simply
229
+
checking for the correct value of the *party* attribute on candidate instances. The *clean_party* code has been nicely
197
230
tucked out of sight. In fact, we emphasize that this method is an *implementation detail* --
198
-
part of the candidate class's internal housekeeping -- by prefixing it with two underscores.
231
+
part of the *Candidate* class's internal housekeeping -- by prefixing it with two underscores.
199
232
200
233
This syntax denotes a [private method][] that is not intended for use by code outside the
201
234
*Candidate* class. We're restricting (though not completely preventing) the outside world from using it,
202
-
since it's quite possible this code wil change or be removed entirely in the future.
235
+
since it's quite possible this code wil change or be removed in the future.
203
236
204
-
> More frequently, you'll see a single underscore prefix used to denote private methods.
237
+
> More frequently, you'll see a single underscore prefix used to denote private methods and variables.
205
238
> This is fine, though note that only the double underscores trigger the name-mangling
We now have two sets of code (and related tests) for the same functionality. But we're not yet ready
243
+
We now have two sets of code (and related tests) for the same functionality. But we're not quite ready
211
244
to delete the original *clean_party* function in _lib/parser.py_. Ideally, we'll delete that code and its tests
212
-
*after* we've written tests that exercise the summarization logic. That way, we'll have greater confidence that converting from a function-based to a class-based strategy hasn't corrupted the summary numbers.
245
+
*after* we've written tests that exercise the summarization logic. That way, we'll have greater confidence that
246
+
converting from a function-based to a class-based strategy hasn't corrupted the summary numbers.
213
247
214
248
##### Questions
215
249
216
250
* In order to migrate functions to methods on the Candidate class, we had to
217
251
make the first parameter in each method *self*. Why?
218
252
219
-
##### Exercises
220
-
221
253
### Add vote
222
254
223
-
Each candidate has a single name and party (well, usually), and numerous county-level results.
255
+
Each candidate has a single name and party, and numerous county-level results.
224
256
As part of our summary report, county-level results need to be rolled up into a racewide total for each candidate.
225
257
At a high level, it seems natural for each candidate to track his or her own vote totals.
226
258
227
259
Below are a few other basic assumptions, or requirements, that will help us flesh out
228
-
vote-handling code on the Candidate class:
260
+
vote-handling on the Candidate class:
229
261
230
262
* A candidate should start with zero votes
231
263
* Adding a vote should increment the vote count
232
264
* County-level results should be accessible
233
265
234
266
With this basic list of requirements in hand, we're ready to start coding. For each requirement, we'll start by
235
-
writing a (failing) test that captures this assumnption; then we'll write code to make the test pass. The goal
236
-
is to capture our assumptions in the form of tests, and then write code required to meet those assumnptions.
267
+
writing a (failing) test that captures this assumption; then we'll write code to make the test pass. The goal
268
+
is to capture our assumptions in the form of tests, and then write code to meet those assumnptions.
237
269
238
270
1. Add test for zero vote count as initial Candidate state ([elex4.3.0][])
239
271
240
272
> Note: We created a new *TestCandidateVotes* class with a *setUp* method that lets us
241
273
> re-use the same candidate instance across all test methods. This
242
274
> makes our tests less brittle -- e.g., if we add a parameter to the
243
-
> Candidate class, we only have to update the candidate instance in
244
-
> one the *setUp* method, rather than in every test method (as
245
-
> we will have to in the *TestCandidate* class)
275
+
> *Candidate* class, we only have to update the candidate instance in
276
+
> the *setUp* method, rather than in every test method (as
277
+
> we will have to do in the *TestCandidate* class)
246
278
247
279
1. Run test; see it fail
248
280
1. Update Candidate to have initial vote count of zero ([elex4.3.1][])
@@ -282,6 +314,8 @@ is to capture our assumptions in the form of tests, and then write code required
0 commit comments