Link to wealthfront.com



Like what you see here? Join the Wealthfront engineering team! Fork me on GitHub

Wednesday, December 22, 2010

Lean Startup for Geeks at Wealthfront: Miško Hevery on the Psychology of Testing

On January 18, 2011, at 6pm, Wealthfront will host the 3rd talk in our "Lean Startup for Geeks" series. We will be welcoming Miško Hevery, a well-known agile coach with many contributions to the open-source world such as AngularJS, the Testability Explorer, and the js-test-driver. Miško, who has inspired countless engineers to test effectively, will be speaking about the Psychology of Testing. After a short presentation, there will be plenty of time for Q&A and mingling.

Wealthfront's UStream channel will be broadcasting the event live. Online viewers will be given a chance to ask questions via Twitter. Follow Wealthfront Engineering's Twitter account for more information as it comes up.

Attendance is by invitation only, place a comment on the post or reach out to us if you wish to have an invitation.

Fun puzzle from Cryptography Research

We got a package from our friends at Cryptography Research today. It was a puzzle inside a puzzle, worthy of being a significant plot device in a Dan Brown novel.

It took us several minutes to open, then the real fun began in cracking the cryptography.
Date: Wed, Dec 22, 2010 at 2:08 PMSubject: Re: crypto research encrypted puzzleCONGRATS! [... remainder removed]On Wed, Dec 22, 2010 at 2:03 PM, Kevin wrote:> Looks like [type of cipher]> > > On Wed, Dec 22, 2010 at 2:02 PM, David wrote:> > ICBJJEVM! [... remainder removed]

Tuesday, December 21, 2010

Using Cumulative Sums to Analyze Financial Data

The goal of modeling is to be able to describe and predict the behavior of a system. When modeling systems where the general form of the model is unknown, one must usually begin by looking at data to get a sense for the relationship between model performance and potentially explanatory variables. Analyzing financial data is a difficult problem, but one that can often be made easier by finding a useful visualization for the data. This post will use Matlab to demonstrate a handy trick for transforming and visualizing financial time series data that can sometimes quickly reveal hidden features.

The example set of data is a set of returns (normalized to mean zero) for a trading algorithm built to emulate a short straddle on a stock from 4/01/09 to 4/13/10. The columns are date, return in dollars, and a measure of the high-low price range of the instrument being traded. Each row is a single day. Our goal is to try to understand the relationship, if any, between the return of the algorithm and the price range of the underlying instrument.

A scatter plot is a good place to start:
figure(1); scatter(exampleData(:,3),exampleData(:,2))
xlabel('Price Range'); ylabel('Return ($)');




Adding the least squares regression line may also be helpful:

A = [exampleData(:,3) ones(length(exampleData),1)];
B = exampleData(:,2);
regrCoefficients = lscov(A,B);
hold on;
plot([0; 1.4], [0 1; 1.4 1]*regrCoefficients,'r');
hold off;



A short straddle tends to make money when the price does not move very much, so a higher price range should lead to a higher loss. Thus, a negatively sloped regression line is expected. Other than that it is hard to tell very much from this plot.

For a smooth noise-free function, the cumulative or running sum of samples of that function is an approximation of a value proportional to the integral of the function, and so looking at the slope or derivative of this cumulative sum gives an approximation of a value proportional to the original function. This is not very useful for smooth data, but for noisy financial data it can be very revealing: the slope of the cumulative sum of the noisy data is proportional to the instantaneous expectation of the data.

Let us sort the data by the price range column, and plot the cumulative sum of the return series:

sortedData = sortrows(exampleData,[3]);
plot(sortedData(:,3), cumsum(sortedData(:,2)));
xlabel('Price Range'); ylabel('Cumulative Return ($)');




The above plot shows distinctly that the expectation of our trading algorithm changes rapidly from positive to negative as the price range moves above approximately 0.5. This is close to the zero of our regression line, 0.4461. Note that plotting the data this way, with our sorted price range column as the x-axis, the slope is not the expectation but rather the expectation times the (unnormalized) probability density: a slope may indicate either a large expectation at that price range and a small probability of encountering that price range, or vice versa. So one way to normalize it would be to divide by a probability density function, which could be determined by "bucketing" the data or could be provided exogenously. Another, quicker, way to get a sense for the expectation on a given interval is to plot the same data with even spacing for each point:

plot(1:length(sortedData),cumsum(sortedData(:,2));
xlabel('Day Number'); ylabel('Cumulative Return ($)');




The peaks of this and the previous plot correspond to the same point, so by looking at both plots simultaneously we can get a sense for the probability density of the price range distribution and therefore say something about the expectation: while there are fewer negative days than positive days, the magnitude of the expectation on negative days is larger. We can see this because the slope prior to the peak is positive and slope after the peak is negative and larger in magnitude. We can also see that there are only approximately 10 points with a price range higher than 0.75, and these make up approximately $8000 in losses, so our algorithm clearly has a problem dealing with outlier days.

Looking at the slopes of cumulative sums can be a quick, informative way to explore the features of a dataset, as long as one is careful to think about exactly what the slope means in a particular instance. Hopefully you will find these tricks handy in your own analysis of noisy data.

Friday, December 17, 2010

Factory > Fixtures

In general, using fixtures in testing is a big no no. Not only does it become a maintenance nightmare, it can also significantly slow down your test suite. Instead, the general practice should be to build out factories.

I personally like using machinist for its terse syntax, but because our front end stack relies on RPC calls for backend data, I thought it would be easier and more fun to build our own custom mocking factory. We use forgery in conjunction with our factory to generate random json data, greatly simplifying the amount of testing setup. This allows the developer to focus soley on satisfying the conditions necessary for a specific test. The factory will randomly generate all other unspecified fields to maintain a valid model object.

For those that are unfamiliar with how a factory can help, here's a quick walkthrough with how BDD testing would've been done. Let's say we need to create a page that shows a customer's valid accounts. We first begin by writing our controller spec:
it "should be able to login and view active accounts" do
  user = User.new("id" => 120)
  login_as(user)
  web_service.expects(:get_accounts_for_user).with(user).returns([Account.new("id" => 4, "userId" => 120, "state" => "PENDING"), 
                                                                 Account.new("id" => 3, "userId" => 120, "state" => "FUNDED"), 
                                                                 Account.new("id" => 5, "userId" => 120, "state" => "OPENED")])
  get :view_active_accounts 
  assigns[:active_accounts].size.should == 2
end
And then we write our controller code
def active_accounts
    @active_accounts = web_service.get_accounts_for_user(@current_user)
  end

Because the total number of QA engineers at our company is a big fat 0, we use RSpec's handy integrate_views option to test all view renders instead of mocking out the render. This helps us sleep a healthy 8 hours on school nights. Lets continue on with writing our view code...

<div>Welcome <%= @current_user.full_name.capitalize %></div>
<div>Your total account value is: <%= sum_market_value(@active_accounts) %></div>
<table>
<tr><br/><th>Account ID</th><br/><th>Account State</th><br/><th>Market Value</th><br/></tr><br/>
<% @active_accounts.each do |account| %>
<tr><br/><td><%= mask_account_number(account.account_id) %></td><br/><td><%= account.state %></td><br/><td><%= number_to_curenty(account.market_value) %></td><br/></tr>
<% end %>
</table><br/>

Now re-running our specs again will probably blow up with failures since we didn't provide some of the key fields. Lets go ahead and add in those fields...

it "should be able to login and view active accounts" do
  user = User.new("id" => 120, "lastName" => "Smith", "firstName" => "John")
  login_as(user)
  web_service.expects(:get_account_for_user).with(user).returns([Account.new("id" => 4, "userId" => 120, "state" => "PENDING", "marketValue" => 100000, "accountId" => "#462462252"), 
                                                                 Account.new("id" => 3, "userId" => 120, "state" => "FUNDED", "marketValue" => 2522, "accountId" => "#3252522"), 
                                                                 Account.new("id" => 5, "userId" => 120, "state" => "OPENED", "marketValue" => 6821, "accountId" => "#9258235")])
  get :view_active_accounts 
  assigns[:active_accounts].size.should == 2
end

The specs will work now, but that doesn't prevent another coworker from exposing another field on this page in the future. He/she will then have to look into this test and make changes in order to make the spec pass again. As a developer, this gets annoying because I'm not specifically testing this particular view--in fact, I may have to test this exact same page again with different conditions. All that I'm really concerned about for this test case is that there are 2 active accounts for my user. With our handy factory, we can now specify the code as follows:

it "should be able to login and view active accounts" do
  user = User.new_mock
  login_as(user)
  web_service.expects(:get_account_for_user).with(user).returns([Account.new_mock("userId" => user.id, "state" => "PENDING"), 
                                                                 Account.new_mock("userId" => user.id, "state" => "FUNDED"), 
                                                                 Account.new_mock("userId" => user.id, "state" => "OPENED")])
  get :view_active_accounts 
  assigns[:active_accounts].size.should == 2
end


Since we don't care about any of the fields for either the User or the Account objects, they technically can be made up of any valid data. All that we are testing for is that the active accounts should be selected. This allows engineers to only have to be explicit for what they are actually testing for.

For any of you that are interested in how we created our own factory, here is the code with some sample blueprints of the User and Account models.

class Object
  def self.blueprint
    hash_result = yield
    self.to_s.constantize.module_eval("def self.blueprint_defined() #{hash_result.inspect} end")
  end

  def self.new_mock(json_hash = {})
    self.new(self.mock_json(json_hash))
  end

  def self.mock_json(json_hash = {})``
    self.blueprint_defined.merge(json_hash)
  end
end

User.blueprint do
{
  "id" => Forgery::Basic.id,
  "lastName" => Forgery::User.last_name,
  "firstName" => Forgery::User.first_name,
  "address" => Address.mock_json
}
end

Account.blueprint do
{
  "id" => Forgery::Basic.id,
  "userId" => Forgery::Basic.id,
  "state" => "FUNDED",
  "marketValue" => Forgery::Portfolio.market_value,
  "accountId" => Forgery::Portfolio.account_id
}


Notice that we can also nest json models within one another. The ultimate goal for creating tools like our blueprint factory is for both maintainability and ease of use. Writing specs may not always be fun, but they are a necessity. And anything that can help us stay efficient is a big win.

Wednesday, December 15, 2010

Expressive Java & Fluent Interfaces

In this article I briefly explore Wealthfront's extensive usage of expressive and fluent interfaces in Java such as Kawala, Guice, Mockito, and Guava. To the uninitiated, Java invokes parables of moronic XML, obtuse syntax, and Nile-like constructor calls. As a result, Java's status as a living language is often questioned and it's "uncoolness" is dogmatically spread. A common characteristic of programming languages and frameworks considered "cool" by the Silicon Valley hacker scene is expressiveness. I will show that modern Java frameworks such as Kawala, Guice, Mockito, and Guava corroborate Java's adaptability. Java's ability to give voice to newer, more expressive programming styles (despite political tomfoolery and slow progress on the, new, Java 7 specification) indicates that it will remain a leading choice for applications of any size.
Providing a definition of expressiveness is left as an exercise to the reader. Here, instead of a definition we'll provide a few representative examples of non-Java expressive programming interfaces.
Node.js is a chief example of an emerging, “trendy” programming framework, with a non-mainstream, following. It capitalizes on Javascript's conciseness to bring forth prose-like readability. The results are best exemplified by its Hello World example:
var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('=Hello World\n');
}).listen(8124, "127.0.0.1");
Similarly, RSpec is a well-respected (and trendy) behavior-driven development tool for Ruby that integrates aspects of Test-Driven Development (which we reverently practice here at Wealthfront). The fact that its "Get Started Now" example is amply self-descriptive and can be presented with no further commentary speaks to its expressiveness:
require 'bowling'
describe Bowling, "#score" do
it "returns 0 for all gutter game" do
bowling = Bowling.new
     20.times { bowling.hit(0) }
bowling.score.should == 0
end
end
In both cases, syntax lends itself to form a localized miniature domain-specific language to effectively express the concern at hand, and nothing further.

At Wealthfront, we believe that unit tests serve as self-correcting alive documentation that is guaranteed to evolve along with the code it accompanies. Describing the contract and testing the implementation at one pass is easy with Mockito's highly expressive interface. Here's an example for the imaginary NasdaqMarket class. The expectation is that NasdaqMarket will disallow scheduling trades to be executed during non-trading hours:
import static org.mokcito.Mockito.*;
[...]
TradingSchedule schedule = mock(TradingSchedule.class); 
final DateTime weekend = [...]; 
when(schedule.canTradeAt(weekend)).thenReturn(false); 
NasdaqMarket m = NasdaqMarket.withTradingSchedule(schedule).build(); 
assertFalse(m.canScheduleTrade(weekend, SELL, 100, NASDAQ_WLTH)); 
verify(schedule).proposeAllowedDate(weekend);
Mockito along with a builder pattern for NasdaqMarket make this test extremely readable and concise. The contract defined in this test is unambiguous (as guaranteed by your friendly Java compiler): Given a DateTime that trading is disallowed, NasdaqMarket will refuse to schedule a trade. Furthermore, it's guaranteed to propose to the Schedule class that said DateTime is allowed.

A similarly expressive Java framework is Guice. Guice allows the clear separation of behavior from dependency resolution. "A [Guice] module is a collection of bindings specified using fluent, English-like method calls" (Guice, Getting Started). Indeed, the binding specification interface speaks for itself:
bind(Integer.class).annotatedWith(named("login timeout seconds")).toInstance(10);
bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class);
bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
bind(Market.class).to(NasdaqMarket.class);
Requesting that a constructor receives its dependencies via dependency injection is as easy as specifying the @Inject annotation. Wealthfront's entire backend application stack, processing immense amounts of market data daily, relies heavily on Guice for dependency resolution and configuration.

Guava allows the easy construction of type-safe ImmutableLists with a quick static call such as List s = ImmutableList.of("A", "B", "C"). Guava includes a variety of functional methods to operate on its collections. For example, one could easily filter a list of integers based on parity:
List<integer> x = Iterables.filter(input, new Predicate<integer>() {
public boolean apply(Integer input) {return (input % 2) == 0;}});
Our very own Kawala, provides a variety of fluent and concise utility interfaces that should make your Java look like an avid reader of Vogue. Take a look at our previous blog post on the LessIOSecurityManager, to ensure awareness of hidden I/O operations in your application, and note its fluent annotation-based interface.

Martin Fowler's discussion of Fluent Interfaces provides a few more examples and the discussion continued on Piers Cawley's blog. Although the idea of fluent and expressive interfaces in Java has been around for more than half a decade, it's only catching up now, after the "re-discovery" of beautifully designed interfaces in emerging frameworks. I believe it's the duty of every Java coder, engineer, and architect, to embrace fluent and expressive interfaces in an effort to rid Java of the (now obsolete) rumors about obtuse interfaces and unintuitive design.

Tuesday, December 14, 2010

Using Monadic Rainbow Tables for Eventually Consistent Higher-Order Types


Just kidding.

David made us Gin Fizz's and Whiskey Sours at last night's poker game. Delicious.

Friday, December 10, 2010

IRC bots for Laziness, Impatience, and Continuous Deployment

1988 was an epic year. Not only were the movies "Die Hard", "Coming to America" and "The Naked Gun" released, but according to Wikipedia, IRC was invented. A tried-and-true technology, constantly reincarnated in similar (perhaps inferior?) forms, IRC is a superb communications hub for a company because it affords so many things:
  • Everyone can chat about coding, operational concerns, and the weird things non-engineers do.
  • Folks can be connected if working remotely.
  • You can store a record of those conversations, events and actions to create an "institutional memory".
  • Bots (computer programs) can join the chat rooms to do our bidding.
The last point is sometimes under-appreciated, because IRC can then become a "networked command line". To that end we've hooked up our epic Deployment Manager and a few other services as IRC bots. These bots keep us informed about our continuous deployments and the state of the production systems, and allow us to quickly and easily react to any situation. Writing IRC bots is a virtuous activity.

Our bot lineup includes:
  • "DM": (Deployment Manager) Build breakage/fixes, deployments, roll backs, service instance bounces and leadership changes.
  • "RS": (Report Service) Notifies us of operational reports that need our attention.
  • "ESP": (Esper Service) Exception alerting and clearing, live site stats.

Monday, December 6, 2010

Trust, but Remind

Every engineering organization faces the problem of how to ensure that things are done right. At Wealthfront, our appoach is a safety net of extremely thorough automated testing that guides the engineer to do the right thing.
  • Automation is consistent. It doesn't skip a code review because we're in a hurry. It doesn't fail to notice a problem.
  • Automation is leveraged. When we write a bug detector or monitoring tool, those will automatically apply to all future code we write.
  • Automation is impersonal. Our build server doesn't think it's being pedantic failing a build because you have ugly formatting, and it's a lot easier psychologically to have a computer give you a list of your failings than a colleague.
The automation is roughly divided into three stages. Unit tests ensure that your code does what you intended it to do. Static analysis ensures that your code is doing things the right way, without doing anything in a dangerous way. Monitoring ensures that your code is still working in production.

If this were a post about TDD, I'd differentiate between unit, functional and integration tests, but in this case it's fine to lump them anything that tests whether your code is doing what you intended together as unit tests. We test using JUnit. It has good integration with our other tools -- Eclipse, Ant and Hudson. We use jMock for mocking, and HSQLDB in-memory database with DbUnit for testing persistence.
assertBigDecimalEquals

We also have a pretty well developed set of internal helpers that help cut out the boilerplate. To start with we have a centralized Assert class offering helper methods like assertBigDecimalEquals and assertXmlEquals for objects that assertEquals doesn't behave well on. In these methods, we relax our habits of using very strict types. While in production code we'd be appalled to be passing around a double just for the purpose of turning it into a BigDecimal, inside a test, this makes things much more readable.

Since testing persistence and clearing the database between tests is easy to get wrong, we encapsulate this inside an abstract PersistentTest. Specific tests just need to indicate which groups of entities they need defined and everything will work. Extending PersistentTest also allows you to use Guice to inject object repositories into your tests rather than creating them.

Many of our entities have long chains of dependencies in terms of a Position is composed of Lots that belong to Portfolio and so on. To simplify these we have factory classes using the builder pattern that can create the object either detached or within a Hibernate session. These aren't any deep voodoo, just a collection of convenience methods and builders that ensure if you just need a Position, it can create some appropriate related data to make things work.

Together, all of these helpers make it easy for engineers to write tests that read like documentation. Everything not central to what's being tested is abstracted away, and the test focuses on "if I have x, and I do y, then I expect to have z".

Of course, none of that is going to guarantee that tests get written. In this we take a strategy of "trust, but remind." We trust the engineer to make sure that the code gets tested to an appropriate level -- a tool used internally to get some statistics obviously needs less testing than our code that calculates the appropriate trades based on a manager's model portfolio. We remind people to write tests in two ways. First, any check-in of a new class in src that doesn't add a new test sends an email to the team saying "Hey! I (kevinp) forgot to add tests for new java or scala class in revision: r42626 or add #testnotneeded to the commit message. I'm going to reply to this thread in a bit and explain why." Secondly, we have a test that does static analysis to ensure that all Queries either have a test or are listed as exceptions. Queries are the functional building block that compose our services.

This minimal oversight ensures that we don't stray too far from testable code. It's immediate and it forces the author to make a choice of "this is somehow hard to test but also really simple so it doesn't need testing, so I'll add an exception and say #testnotneeded" or "fine, I'll go write a simple test that exercises the simple expected path through the code". Writing any sort of test at least ensures that the code doesn't have strange dependencies on hard coded paths or global state, documents the expected behavior, and provides something to build on later when a bug gets found or behavior needs to be changed.

We do two types of static analysis. The first type is a collection of tests based on the declarative test runner from Kawala (our open source platform). These run as part of the main build and a failure breaks the build and stops deployments. The second type are two open source static analysis tools, FindBugs and PMD. These run as part of a separate analytics build and does not prevent deployments. It still sends you nagging email until it's fixed, of course. The goal is to detect whether you're doing things the right way. Presumably since your code has passed all the unit tests, it works, but maybe it has more subtle bugs or is likely to be unmaintainable in the future.

Dependency test tries to enforce good package structure. It lets you express that, for example, everything is allowed to depend on com.wealthfront.utils, but only certain packages are allowed to depend on com.wealthfront.trading.internal. Visibility test lets us annotate methods or classes as @VisibleForTesting to express the difference between "this is public, anyone can use it" and "this is public because I need to access it from another package in a test".

ForbiddenCallsTest
JavaBadCodeSnippetsTest, ScalaBadCodeSnippetsTest, and ForbiddenCallsTest look for things that are so error-prone or so likely to be a bug that you should never use them. For example, we forbid importing java.net.URL because of its surprising hashCode and equals behavior. The only exceptions are a few places dealing with networking or the class loader.

The snippets tests are source based and run using BadCodeSnippetsRunner. These match on regular expressions, and they also enforce some style rules, for example putting a space between "if", "for" or "while" and the opening parenthesis. This isn't the main purpose of the test, but if you are going to adopt a coding standard, and your tools make it easy to enforce, then go ahead and enforce it.

ForbiddenCallsTest is bytecode-based, which allows us to do things like disallowing org.joda.time.LocalDate.toDateTimeAtStartOfDay() (dangerous because it implicitly uses the default time-zone) while allowing org.joda.time.LocalDate.toDateTimeAtStartOfDay(DateTimeZone).

This isn't as easy to use out of the box as PMD or FindBugs, but you can use it without much effort. DeclarativeTestRunner lets you set up a bunch of rules that will be matched in turn against a set of instances, and any failures will be reported. Since it's a generic framework for writing declarative tests that iterate over a series of files, it's more flexible than tools like PMD that only expose a simplified version of the AST. On the other hand, you can't easily duplicate some of the non-local tests that FindBugs can do like checking for inconsistent synchronization.

Some of the tests we have using this framework are more specific to our project. The QueriesAreTestedTest I mentioned above uses DeclarativeTestRunner, as do a test ensuring that classes annotated as JSON entities are actually marshallable, tests that all methods used by the front end (a separate jRuby on Rails project) as still present with the same signatures, and tests that no entities (Hibernate or JSON) attempt to @Inject any dependencies.

Since all of these tests will fail the build, we have a test suite defined containing 10 "pre-commit tests". It's usually only necessary to run the tests for whatever you are working on and the pre-commit tests, and you can be about 95% sure of not breaking the build. It's not actually necessary to run the full 7000-test suite before every commit.

Hudson Analysis Plugin
FindBugs and PMD are two widely used bug pattern detectors. To some extent they overlap, but they detect slightly different things so it's worth using both. We added FindBugs first and PMD afterwords. We haven't yet eliminated all PMD warnings. We have about 200 remaining, mostly issues that don't affect the correctness of the code, like "collapsible if statements" meaning that you can combine the conditions into a single statement to make the code clearer. It's a perfectly valid point, but not really a high priority to fix.

FindBugs and PMD (and several others) integrate very well with Hudson CI. The Analysis plugin will display charts of high, medium and low priority warnings, changes from the previous build, and fail the build based on either absolute thresholds or when the number of warnings increases.

Good code that triggers a PMD warning.
Resolving failures or warnings reported by any kind of static analysis doesn't always mean changing your code; often it means adding exceptions. I've seen people recommend elaborate systems involving taking a vote on how important each warning is and whether the rule should be excluded, but this seems like overkill. At Wealthfront, exceptions are easy to add. Just open up the file and add an exception, or annotate with @SuppressWarnings, as appropriate. This goes along with "trust, but remind". The static analysis is your helper, not your master. Making it easy to add exceptions lets us be strict with the rules. Being strict with the rules gives the engineer a little reminder when they stray into dangerous territory. It forces you to think "hmm... do I really want to make our core data model have a dependency on this vendor specific package?" If you make exceptions too hard to add, everyone will preemptively exclude any rules likely to need exceptions.

Testing doesn't stop when code gets to production. I'll save the details for another post, but the key is everything is automated and there's no human tasked with finding the problems. A human is only asked to investigate once the system has found some reason to suspect a problem. Machines are very good at tedious repetitive tasks like checking a service is up, or checking that a report got generated. Humans are good at exercising judgment whether something reported is an actual problem, as long as the volume of alerts is manageable.

Unit tests verify your code does what you intended it to do. Static analysis ensures you aren't doing it in a way that will cause problems down the line. Monitoring ensures that your code is still actually doing the right thing once it's in production.

Combined, these tools allow us to keep going forward with improvements without needing to stop to fix bugs or horribly flaky code too often. Of course, we still need to reorganize code because it's grown to do something other than what it was intended, and that's where the automation really shines. All the folk wisdom around the office of "oh, everyone knows you can't use a static SimpleDateFormat" is embedded in code, so we avoid making the same mistake twice.