Here are my notes from Stuart Halloway's keynote at EuroClojure 2013.
For this one you could say it was all about the delivery. I won't even try to replicate it here, but instead hope a video of some version of this talk will surface sooner or later.
- If you want to be the hero, you'll need to write code that no one can understand.
- Writing bad code is not easy to do in a way that isn't obviously bad.
- You could embrace the weirdness of your local language, e.g. by going nuts with type hierarchies of checked exceptions, or by going overboard with naming conventions like Spring has done (
- You want to be irreplaceable but you want them to also love you. Make use of the fact that software is a connected universe. Make your code complex in a way that the symptoms appear somewhere else – for people upstream or downstream of your code.
- Here are some specific ideas for achieving this.
Embrace setter methods
- Java's polymorphism in interfaces is a technique for simplification. There's also another great thing in Java, which is a lot more subtle: At the end of a constructor run, you're guaranteed to have an object in a valid state.
- With setters you can ruin all of that: The constructor guarantees are no longer in place. And you can't just check validity in setters because of hierarchies in the data (they're not atomic).
- Setters undermine the best part of OO. They also undermine interfaces, because you can't enforce interface contract.
- In object graphs this mess propagates through object connections.
Prefer APIs over data
- JUnit produces an XML document to describe tests. Other people can write things that process that with no dependency on JUnit or even Java, and without being coupled in time. It's a beautifully simplifying thing.
- If they'd done an API instead, all sorts of complexity: A reporting API would be temporally coupled to the running JUnit JVM, the language, with its mutability and other esoteric features.
- In another part of JUnit they have chosen an API over data: Tests are specified with classes and annotations (an esoteric feature). Basically turning data into classes. This is an example of the complex thing. Tight coupling.
- The next level: Always start with DSLs. Nothing says "screw you" like a DSL.
- In a good, simple design you think of information first, then the API, then a DSL. Invert that for narcissistic design. Start with macros! Just make a DSL, don't even think about the API, or the information model.
- SQL specified a DSL without a specified API or an information model. With SQL you have to rebuild APIs on it like JDBC. (good ol' little bobby tables)
- The JVM classfile format is a documented (binary) data structure. You could write tools for it in any language. No API or DSL. It was key to the success of Java as a language.
Always connect, never enqueue
- OO facilitates method calls on objects - presumes that objects are always available and close.
- Since that's not always true, queues can help.
Create abstractions for information
- Data really only comes in a few shapes (scalars, seqs, arrays, maps, sets, etc.)
- In a narcissistic design every new piece of data should have an abstraction (Order, Line item, Customer, etc.)
- In addition: Setters everywhere - no idea how the code works. Then use getters to block access to the data. Codebase instantly becomes a magnitude larger.
- Concretion - Unnecessary data abstraction for other people to deal with.
Use static typing across subsystem boundaries
- Static typing is like your underwear - others shouldn't be looking at it.
- When intermediaries are passing around statically typed things, and something about the type changes, all intermediaries break.
- Rich Hickey's analogy: What is the static type of the UPS truck? Save it in your object, so it's precisely 12 inch shells of orange juice. Not milk or anything else.
- On the JVM, we have checked exceptions showing what went wrong using types. 40 subclasses of exceptions distinguish what went wrong. But users don't care - most of the time they just can't do anything about it, and the rest mostly fits in a few categories.
Put your language semantics on the wire
- Make your language semantics everybody's problem. Put type systems and language semantics on the wire.
- Java RMI, CORBA, DCOM.
- "Scalable" has an actual meaning, but often misused. From business perspective it's a general feelgood word. You might even name a language after it.
- "You know what is web scale? The web. Oh, and it is dynamically typed."
- TIOBE index: C, Java, C++, ObjC, PHP, C#, VB, Python, JS, Transact-SQL, VB.NET, Perl, Ruby. Everyone knows some distinguishing characteristics these languages have.
- On the other hand: Avro, bson, csv, edn, fressian, hessian, json, kryo, protobuf, thrift, yaml, xml. With these we're much less capable of discussing the tradeoffs, characteristics.
- Local language semantics vs. data semantics. Which of those kinds of languages is on the wire? Which one is worth thinking about more?
- JSON is the enemy of complexity. Incredibly spartan. We are challenged to make that complex: "Put JSON into APIs so its improverished semantics become everybody's problem".
Write lots of unit tests
- Every unit test you write is not production code solving someone's problem.
- Steps in example-based testing: Setup, inputs, execution, output, validation.
- Good OO design says you wouldn't write it like this, all in one place. Instead you could decouple all these steps.
- Decoupling the setup could improve design. Decoupling the input could provide more comprehensive tests with more data. Decoupling execution could allow reuse on different levels of tests. Decoupling outputs could allow for further analysis and persistence of them.
- "Use static typing and unit testing as an expensive way to catch easy bugs".
Update information in place
- Transient/mutable data structures are motivated by things like: Memory is expensive, storage is expensive, machines are precious, resources are dedicated.
- Everything about writing software sucks with transient types. Sharing is hard, distribution is hard, concurrency is hard, access is always eager, caching is difficult.
- Java, .Net, relational and NoSQL databases pretty much all work like this.
- With persistent data structures: Sharing is trivial, distribution is easy, concurrency is trivial, access can be eager or lazy, caching is easy.
- Clojure, F# collections, and Datomic work like this.
- Mutability is used for modeling the substrate on which programs run (mutable RAM, mutable disks). Most of us aren't really building that.
- A reason for mutability often given is the ability to see change over time - time model. But OO delivers all the complexity of mutability and no time model whatsoever.
- Next level maneuver for narcissistic design: Eventual consistency. The fast path to complexity. You don't really know what's going on.
- Active Record (the pattern) - in Fowler's picture the line that connects the record to "the cylinder" and the cylinder itself don't have any names. They're basically the connection string and the table name. The reason they have no name is that they're contextual. When you read the code, you don't see the connection or the table name there either.
- In the Rails implementation of ActiveRecord, stack overflow searches reveal all kinds of problems: "Now I need to talk to two databases -> Override the contextual data. Upgrade ActiveRecord, those overrides fail. They've changed the time semantics changed between versions. The order matters, and an internal library change broke it. Migrations change too, adding Rake also into the picture..."
- Two good examples of these principles: Build tools and ORMs.
- In agile projects, as a developer, you sometimes get frustrated by the product owner. They don't seem to have deep enough tech knowledge of the thing. "Really wish we could be the stakeholder". If you want to see real complexity, look at projects where the world's worst stakeholders - developers - are product owners. These are build tools and ORMs.
Superstructure around broken things
- In a narcissistic design, never eliminate complexity, automate around it.
- If there's something broken beneath you, write more code to superstructure around it.
- Clojure gets patch proposals for fixing things that are broken in Java. They're always rejected but they would be great for making Clojure more complex. (example: "Fixing" Java's multiple instances of Boolean)
- Message by pull request, because code is the first and best unit of discussion.
- This says to the world: The process of doing things in this software is all about code.
- When you want to build a simpler system, it doesn't really work that way. Clojure and the kernel team are examples.
- GitHub pull requests are references, not values. They have mutable semantics.
- Git is wonderful. Managing software projects by pull requests is an opportunity to pervert its intent.
Know Your AngularJS Inside Out
Build Your Own AngularJS helps you understand everything there is to understand about AngularJS (1.x). By creating your very own implementation of AngularJS piece by piece, you gain deep insight into what makes this framework tick. Say goodbye to fixing problems by trial and error and hello to reasoning your way through them