Slide Of The Conference 7
Britt Selvitelle of Twitter gave a cracking talk at RailsConf Europe about scaling Rails applications to Twitter scale. It was great. Full of advice that we shall definitely be taking on board as we continue to develop amazing tunes. However, the last slide before the inevitable “Any Questions?” was the slide of the conference. It read:
It’s 2007. Every spammer has your email address. Put it on your goddamn webpage so people can get ahold of you about interesting things
I was the only person in the room who applauded.
I’m enough of an old fart (I was 40 a couple of Saturdays ago. I don’t think I’m properly old, but, on the net it feels positively ancient) that I remember getting annoyed at people obfuscating their sender addresses so that I couldn’t simply hit r in rn to reply to them directly rather than wibbling on channel. This was especially annoying when I was replying with an answer to a question they’d asked.
In the (my stats tell me) unlikely event that you’re reading this directly on the webpage, you’ll see my email address, complete with mailto: link. The wayback machine tells me it’s been there since at least February 2006, I’ve been using the same email address, unobfuscated, on mailing lists, newsgroups, business cards and presentation slides for over 10 years now. Yes, I get spammed, so will you. So do you, I’ll bet. But I’ll pay the cost of making it easy for spammers if it also makes it easy for people I want to hear from to get in touch with me. Spammers are predictable; that’s why spamfilters work most of the time. Interested people – people who are prepared to send me relevant mail – are golden.
The marketing types will tell you that clicks cost money. Every screen your customer has to go through before you get her money is another place she could decide she has better things to do. And, presumably, she is going to get something tangible at the end of that process so she’s motivated to go all the way through the process.
Someone who wants to send you email has far less motivation than that. The reader who thinks, “Ooh, Piers has missed something there, but I the comment box is the wrong place to bring it up.” or “Ooh, Piers Cawley, I haven’t seen him since we were at university, I should drop him a line” will be far more likely to act on that impulse and send that mail if it’s one click away. If they have to go and google my address from an obscure newgroup, or crack pdcawley [at] bofh [dot] org [dot] uk, or some other fatuous scheme designed to hide me from bad guys who already know where I live…
You’re intelligent people; you know what’s going to happen.
Seriously, would it kill you to publish your contact details on your website?
Tangentially
One of the RSS feeds I follow is a technorati feed of blogs linking to Typosphere. It’s a reasonably good way of seeing what people are saying about Typo (even if it’s simply “I’m switching to Mephisto/Wordpress/Whatever”). However, a few days back, the feed told me that someone had said:
Sorry, comments are broken. Please send complains to Typo.
So, being a conscientious Typo maintainer, I thought I’d drop him a line and ask for a few more details to see if we could track down the root of his problem. No email on the page. No obvious pointers to an email address.
He might be paranoid on his blog, but he’s not paranoid enough to avoid spammers. I found his email on the first page of google results, but I only bothered to look because I was inspired by Britt’s slide to write this. I guess I should drop him a line and see if we can’t sort it out.
My head hurts 3
During DHH’s keynote at RailsConf Europe it was apparent that there’s a great deal to like in edge rails, so I thought I’d have a crack at getting Typo up on it.
Ow.
I’d expected the pain points to be related to routing, but it seems that the rails routing system is approaching the level of the Excel calculation engine – nobody dares touch it for fear of breaking things, so typo’s custom routes seemed to work quite happily. There were a few things that have been deprecated, pluginized or moved out of the set of modules that’s automatically included when you do a rake rails:freeze:edge, but they were pretty easy to sort – the deprecation messages are a good deal more informative now than they were last time went deprecation squashing. There’s a surprising amount of stuff that’s been removed without any deprecation warnings though, which isn’t very sporting. DHH said there would likely be a 1.2.4 release (possibly a day before 2.0) with a bunch more deprecation warnings covering everything that’s actually going away, so if you’re thinking of moving a maturish app to Rails 2.0 it might make sense to wait for 1.2.4, install that, squash warnings, and move on up to 2.0.
The real pain comes from themes. Typo’s themes rely on Rails internals working in a particular way, but they don’t work like that any more. In theory, the internals appear to be more theme friendly, related to allowing plugins to include views. The problem is, that it’s possible to change Typo’s theme without restarting the server, and the new themish internals don’t expect anything to change until the server’s restarted.
So, I’ve been playing with plugins. The most promising approach appears to be that of the themer plugin, which gets pretty close to doing what we need, and does it in a way that seems like it should work with both 1.2.3 and Edge Rails. It does appear to be making some radically different assumptions about the structure of the themes directory, but the basic framework is good and I should be able to make things work by making our current them object conform to Themer::Base’s interface and duck type my way to the sunny uplands of Edge Rails compatibility.
Which will be nice.
I like the themer approach a lot. Instead of monkeying about in the guts of rails, it monkeys about in front of Rails. It overrides render so that you can pass it a theme/lookup object. If it sees a lookup object, it uses that to rewrite the rest of the render arguments into a form that will render the right thing using the standard implementation of render. In a work project I’ve taken a similar approach to handling polymorphic routes for things like:
map.resources :pictures do |pics|
pics.resources.comments
end
map.resources :users do |users|
users.resources.comments
endI ended up with a to_params method defined on my Comment model, and stuck an extended url_for in front of the default Rails version, which looks something like:
def url_for_with_to_params(*arguments)
if arguments[0].respond_to?(:to_params)
with_options(arguments.shift.to_params) do |mapper|
mapper.url_for_without_to_params(*arguments)
end
else
url_for_without_to_params(*arguments)
end
end
alias_method_chain :url_for, :to_paramsWhich is so much neater than the last time I attacked this particular problem (see the acts_as_resource plugin).
One of the nice things about Rails is that, although it’s opinionated and somewhat liberal with the syntactic vinegar for things the core team don’t think is the Right Way, they’re pretty good at leaving the door open for people like me who have other opinions. Both the themer plugin and my as yet unpluginized extension of url_for work by using existing capabilities in new ways and, because those capabilities are documented we can expect them to continue to work over multiple versions of Rails. Plugins that achieve similar effects by monkeying with Rails’s internal interfaces are hostages to fortune. Internal interfaces are free to change at any time, even between point releases, so a plugin can be left high and dry with surprising rapidity. Just ask the Rails Engines folk.
Hello Berlin!
Well… that was a fun flight. We’ve missed the Bratwurst on Rails event that the Berlin Ruby folks were putting on, so it’s a simple matter of getting some sleep before heading over to the conference hotel at ungodly o’clock to register in time for the first of the Railsconf tutorials.
If anyone knows of a good professional photolab in Berlin that can do black and white film dev + scan overnight, I’d be very interested to hear about it. I’ve managed to cover the last couple of O’Reilly European conferences in a timely fashion without having to go digital and I don’t want to break that record.
A tiny ruby niggle 32
You know what? I’m starting to miss compulsory semicolons as statement terminators in Ruby.
“What?” I hear you say. “But not needing semicolons is one of Ruby’s cardinal virtues! Are you mad?”
I don’t think so, but maybe you’ll disagree after I explain further.
Here’s a piece of code that I might write if semicolons were the only way of terminating a statement:
Category.should_receive(:find_by_permalink)
.with('foo')
.and_return(mock_category);Or how about a complex find query
def find_tags_for(tag_maker, order = 'count')
klass = tag_maker.class
find :all
, :select => 'tags.*, count(tags.id) count'
, :group => Tag.sql_grouping
, :joins =>
"LEFT JOIN taggings ON "
+ " tags.id = taggings.tag_id "
+ "LEFT JOIN bookmarks ON "
+ " bookmarks.id = taggings.taggable_id "
+ " AND taggings.taggable_type = 'Bookmark' "
+ "LEFT JOIN #{klass.table_name} ON "
+ " #{klass.table_name}.id = bookmarks.#{klass.to_s.underscore}_id"
, :conditions => conditions_for(tag_holder)
, :order => (order == 'count')
? 'count(tags.id) desc, tags.name'
: "tags.name"
, :readonly => true
;
endI first came across the idea of the leading comma in Damian Conway’s excellent Perl Best Practices. The idea is that, by leading with the comma it’s very easy to add a new argument to an argument list or hash specification without having to remember to stick a comma on the end of the preceding line if it was at the end, and also, the leading comma makes it very plain that the line is a continuation of its predecessor in some way.
To make the examples work in Ruby, you have to add a \ to the end of each line that has a continuation, so the first example has to be written:
Category.should_receive(:find_by_permalink) \
.with('foo') \
.and_return(mock_category);Lining up the \s helps to stop them disappearing, but it’s an awful faff.
What tends to happen (in the rails source especially) is that ruby programmers simply don’t break their lines up. A quick search of the rails source finds plenty of lines more than 160 characters long.
Of course, some will argue that it doesn’t matter, that the old 80 column limit is a silly hangover from the days of steam when the only way to interact with your code was through an 80 column, green phosphor terminal. They have a point. An arbitrary line limit is silly, and we should get over it, especially in source code. However, unless you’re going to go around with every window open to its maximum width, lines will wrap, and they won’t do it nicely, or respect the indentation conventions of your language. Long lines are murder in diffs too, finding the point of difference is so much easier when your eye doesn’t have to scan an epic line.
It’s a shame there’s no way of forcing ruby’s parser to require semicolons as statement terminators for those programmers like me who think that the restriction that a statement must end with a semicolon is worth the freedom to break lines where we like without needing to escape every line break. It’s a shame too that popular tools like Textmate are so clumsy when it comes to dealing with line breaks. I would attempt to hold Emacs up as a paragon in this respect, but its Ruby mode tends to get a wee bit bemused once you start breaking lines, so that’s no good.
Domino theory
It’s amazing how far reaching seemingly simple language design decisions can be isn’t it? Just getting rid of the need to terminate statements with a semicolon has an enormous effect on they way code in ruby looks. I’m just not sure that they look better.
Maybe Smalltalk got it really right – they chose to use the most valuable syntactic character of all, the space, to denote sending a message. That freed up the . for use as a statement (sentence?) terminator. Then that freed up ; for use in one of Smalltalk’s most distinctive patterns – the cascade. Where a Rails programmer might write:
form_for(@comment) do |f|
f.input(:author)
f.input(:title)
f.input(:body)
endA Smalltalk programmer might eliminate the need for a temporary variable by doing:
Comment>>printOn: html
(html formFor: self)
input: #author;
input: #title;
input: #body.All those input: ... messages get sent to the result of html formFor: self. Once you get the hang of it, it’s a really sweet bit of syntax.
Incidentally, there’s been some discussion on the squeak mailing lists of a companion to the cascade, which would use a ;; as a sort of ‘pipe’. The idea is to be able to replace code like:
((self collect: [:each | each wordCount)
inject: 0 into: [:total :each| total + each])
printOn: aStream.with
self collect: [:each | wordCount]
;; inject: 0 into: [:total :each | total + each]
;; printOn: aStream.(NB: Please ignore what those code snippets do, because that’s gruesome. Concentrate on how they do it).
Nobody’s quite proposed going as far as Haskell does with its Monads, which can be thought of as a magical land where the meaning of the semicolon changes according to what sort of Monad you’re in. (In an IO monad for instance, the semicolon imposes an evaluation order. In some other monad, the semicolon could just as easily denote a backtracking point). Then again, there’s nothing to stop the dedicated Smalltalker implementing something Monadish – every Smalltalk class can specify how its methods should be compiled after all…
In conclusion…
I’m not sure I’ve got a real conclusion for all this. I’m mostly musing. However, I do think it’s useful to think carefully about restrictions and what they free us to do as programmers. Lispers will wax lyrical about the way that their language’s pared down syntax lets them do amazing things with macros. Smalltalkers will defend to the death the idea that the only way to do anything is to send messages to objects. Pythonistas love their syntactic whitespace. Haskellers love their static typing (admittedly, they have an incredibly flexible notation for expressing type that leaves most other programming languages standing).
And any English speaker with ears will know that a poem like Dylan Thomas’s Do Not Go Gentle Into That Good Night gains much of its power from it’s form, the villanelle, one of the most restricted forms of poetry there is. Two lines repeating through the poem and a staggering number of rhymes to find:
Do not go gentle into that good night,
Old age should burn and rave at close of day;
Rage, rage against the dying of the light.
Though wise men at their end know dark is right,
Because their words had forked no lightning they
Do not go gentle into that good night.
Good men, the last wave by, crying how bright
Their frail deeds might have danced in a green bay,
Rage, rage against the dying of the light.
Wild men who caught and sang the sun in flight,
And learn, too late, they grieved it on its way,
Do not go gentle into that good night.
Grave men, near death, who see with blinding sight
Blind eyes could blaze like meteors and be gay,
Rage, rage against the dying of the light.
And you, my father, there on the sad height,
Curse, bless me now with your fierce tears, I pray.
Do not go gentle into that good night.
Rage, rage against the dying of the light.
If that’s not making a virtue of a restriction, I don’t know what is.
Flash and Javascript, sitting in a tree... 7
We’re looking for somebody who can make Flash 8 and javascript play well together on IE 6/7, Firefox and Safari. If you fit the bill, please drop me a line at pdcawley@bofh.org.uk with a pointer or two to examples of your skills and you, me and my boss will have a nice little talk.
We need a flash application which can be controlled via Javascript – we really don’t want to go sticking 10-15 tiny flash buttons on every page if we can help it, just so they can tell the main app (sitting in another window) that the user did something. We have an application that works with Flash 8 and above, but it only works reliably in Firefox. It breaks more or less horribly in IE 6 and 7 and is a wee bit flaky under Safari 2. I haven’t a clue how well it plays with Opera.
Now, on a personal site, I’d be reasonably happy to live with Firefox only and maybe Safari 2, but this isn’t a personal site, so that’s out of the window.
So, if you think you fit the bill, or you want to know a bit more about the problem, please drop me a line. Save me from having to learn ActionScript as well, when I barely know Javascript yet.
Typo stuff
If you’ve been running on the Typo edge recently, you’ll be all too painfully aware that there have been issues with the cache being flushed at the wrong times and not flushed at all at others. Which is not a happy state of affairs.
However, I’ve recently got back to working on Typo and, after a few warmup refactorings and some missteps, I think caching is working properly now.
There’s lots more work to do (there always is) but I reckon that you could do worse than bump your installation to r1513, which is what I’m running here.
Today's Noun Is: Notation 4
Remember back when I wrote about metaprogramming and programming being the same thing?
Well, Libraries, Domain Specific Languages, Domain Agnostic Languages, Pidgins, YAML, .INI files and the like are all the same thing. They’re all notation.
That’s the thing to remember. Good notation makes life easy. Bad (or inappropriate) notation makes it hard (or, in some cases, fun). There’s no point striving to write a pidgin or DSL if your problem can be solved easily enough with a well factored set of loosely coupled classes. After all, even the neatest and cleanest of domain specific languages has to be learned. Meanwhile a class library that’s respects the language of your problem domain can be easier to learn because the notation with which the library is implemented is plain old YFL and your team already knows that (and if it doesn’t, you have bigger problems). Learning the class library is like learning about the problem domain and vice versa. Learning one supports learning the other.
I’m of the opinion that DSLs and pidgins are at their most when their domains are generic. So ActiveRecord’s most useful language like interfaces are related to general problems like expressing the relationships between objects or describing how to validate an object. Backus-Naur Form, Perl compatible regular expressions, parser combinators are all useful for solving the generic parsing and lexing problems that crop up everywhere. Data serialization notations like YAML, FASTA, XML, CSV, JSON and all the others are useful when you want a generic way to pass data between systems. All of these notations are narrow in their focus, but broad in the sense that the things that they focus on are ubiquitous.
The most useful notation for your specific task is almost always a well factored set of classes or functions (depends on your language of choice) which reflects the language of your problem domain. Different parts of your program might well make use of a wide range of ‘donor’ notations, but it’s a rare problem that’s going to require to you implement a full blown language in order to solve it.
Don’t worry about whether something’s a DSL, pidgin or a library. What it is is notation. A better question is is “does this notation help me solve my problem?”. If the answer is no, find better notation. Or keep plugging on with what you’ve got, but don’t fool yourself about your reasons for using the notation – not everyone’s here for the hunting after all.
Today's noun is: Reification 4
Reification: The mental conversion of a person or abstract concept into a thing. Also, depersonalization, esp. such as Marx thought was due to capitalist industrialization in which the worker is considered as the quantifiable labour factor in production or as a commodity. – OED
In the sense that the OED has it, I’m not what you could call a fan of reification. At work, we have a rule that anybody who starts talking about ‘resources’ when they mean ‘people’ gets a (verbal) slap.
However, in OO circles (or maybe just in my head), reification is a good thing. It’s the process of taking something abstract and turning it into a ‘real’ object. Usually, the word gets used for big things like turning an intractable method into an object as a step on the way to refactoring that method. I tend to use it in a slightly broader sense. For me, reification is the process of turning something (a method or a data structure usually) into a full blown object with its own behaviour.
Back when I was working on Pixie (a cunning, but weird, object persistence tool written in Perl) we had a data structure which was used for keeping track of managed objects. It started life as a hash. Everything was fine at first, but over time we ended up with more and more code being repeated across the codebase that was concerned with manipulating the cache hash. So, we replaced the hash with a new object and pulled all the repeated code into methods on that object, which gave us cleaner code to extend, and a strong feeling that we should have turned the cache into an object much earlier in the game. (By leaving it so long, we had a lot more code to move about, some of it in fairly obscure places; tracking down the last bit took a while.)
Data structures like hashes and arrays are really useful in languages that have
them. The catch is, they have this habit of acquiring code. When this
starts to happen, it’s time to reify – to replace the hash with a task specific
object. In Ruby, it’s easy enough to inherit from Hash, but Hash comes with a
pile of methods that probably aren’t relevant to your particular
need. Generally it’s better to delegate. The first cut doesn’t have to be that
complicated, just decorate the hash with a new class and initialize an instance
of the class at the point where you had just made the hash.
Once that’s done, you can go through your code and move the bits that treat the hash as a data structure onto your new class. As you gather all the common behaviour to the new class, you’ll start to see places where you can improve code quality by merging common behaviours, replacing complex conditionals with polymorphism (you’ll probably have to introduce a factory method if you do that) and pulling hash keys out into instance variables.
Stalled reification
Reifying your data structure isn’t an end in itself, it’s a step along the way as you refactor your code.
There’s an example of a stalled reification to be found in
ActionController::Routing::Resources. Consider the implementations of
map_resource and map_singleton_resource, which are the worker methods used
whenever you do a map.resource or map.resources in your routes.rb.
def map_resource(entities, options = {}, &block)
resource = Resource.new(entities, options)
with_options :controller => resource.controller do |map|
map_collection_actions(map, resource)
map_default_collection_actions(map, resource)
map_new_actions(map, resource)
map_member_actions(map, resource)
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, &block)
end
end
end
def map_singleton_resource(entities, options = {}, &block)
resource = SingletonResource.new(entities, options)
with_options :controller => resource.controller do |map|
map_collection_actions(map, resource)
map_default_singleton_actions(map, resource)
map_new_actions(map, resource)
map_member_actions(map, resource)
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, &block)
end
end
end
There’s a lot of repetition there. The only differences are the classes of the
resource object, name of the second function called in the with_options
block. If we take a look at, map_collection_actions things start to look even
fishier. Here’s map_collection_actions, for example:
def map_collection_actions(map, resource)
resource.collection_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
map.named_route("#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path};#{action}", action_options)
map.named_route("formatted_#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path}.:format;#{action}", action_options)
end
end
end
resource.collection_methods.each? Let’s see what happens if we the various
map_foo_actions methods into methods on ActionController::Resources::Resource. While
we’re about it, we can rename map_default_collection_actions to
map_default_actions on Resource, and map_default_singleton_actions to
map_default_actions on SingletonResource, which inherits from
Resource. map_collection_actions becomes:
def map_collection_actions(map)
collection_methods.each do |method, actions|
actions.each do |action|
map.with_options(action_options_for(action, method)) do |m|
m.named_route("#{name_prefix}#{action}_#{plural}",
"#{path};#{action}")
m.named_route("formatted_#{name_prefix}#{action}_#{plural},
"#{path}.:format;#{action}")
end
end
end
end
(we move action_options_for onto resource as well, of course).
Once we’ve moved the various mapping helpers onto the resource classes, we can
revisit map_resource and map_singleton_resource
def map_resource(entities, options={}, &block)
resource = Resource.new(entities, options)
with_options(:controller => resource_controller) do |map|
resource.map_collection_actions(map)
resource.map_default_actions(map)
resource.map_new_actions(map)
resource.map_member_actions(map)
end
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, &block)
end
end
def map_singleton_resource(entities, options={}, &block)
resource = SingletonResource.new(entities, options)
with_options(:controller => resource.controller) do |map|
resource.map_collection_actions(map)
resource.map_default_actions(map)
resource.map_new_actions(map)
resource.map_member_actions(map)
end
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, &block)
end
end
And now, we no longer have two method bodies that look very similar, apart from the resource class, we have to methods that look identical apart from the resource class. So, if we pull out the common bits and put them onto Resource, like so:
class ActionController::Resources::Resource
def install_routes_in(map, &block)
map.with_options(:controller => controller) do |m|
map_collection_actions(m)
map_default_actions(m)
map_new_actions(m)
map_member_actions(m)
end
if block_given?
map.with_options(:path_prefix => nesting_path_prefix, &block)
end
end
end
Then map_resource and map_singleton_resource become
def map_resource(entities, options = {}, &block)
Resource.new(entities, options).install_routes_in(self)
end
def map_singleton_resource(entities, options = {}, &block)
SingletonResource.new(entities, options).install_routes_in(self)
end
Where’s the benefit?
Apart from making the active_record/lib/resources.rb a bit shorter (a
laudable result in itself), where’s the benefit here?
From my own experience of implementing datestamped_resource, a routing
plugin that we use in Typo, it makes the life of anyone writing a resource like
routing helper for Rails a great deal easier. With datestamped_resource I
ended up subclassing ActionController::Resources::Resource, doing the
refactoring I’ve outlined here, but leaving the original Rails methods where
they were and just implementing the ‘moved’ methods on DatestampedResource
(well, not quite, map_collection_actions is pretty different from the
default Resource implementation, but the other actions are pretty much the
same.
In another project I’m working on, I’m trying to retain meaningful urls with
(potentially) deep resource nesting, and it’d be really handy to have an
inflected_resource route helper. The problem with using a meaningful
to_param on your models is, avoiding permalinks that share a name with your
actions. You could set up validations so that, say, ‘new’ is an illegal
permalinks, but it’s clumsy.
However, if you arrange things so that your URLs are inflected, you can always
tell that a URL that begins /resource/new will be a particular resource, with the permalink ‘new’, and /resources/new will be the virtual new resource.
If the resource system is factored as I outlined, this is almost trivial, you can introduce a InflectedResource subclass of Resource
class InflectedResource < Resource
def member_path
@new_path ||= #{path_prefix}/#{singular}/:id
end
end
and you’re pretty much done. Admittedly, something like that (plus a small amount of copy and paste) would work with the current system, but then we’re looking at 3 substantially identical methods in ActionController::Resources and if it wasn’t time to refactor before, it’d definitely be time to refactor then.
Conclusions
Reification shouldn’t be something you do every day, but nor should it be something you do once a flood. Take a look at some of your projects and some of the places where you’re using hashes. Are those really hashes, or would they benefit from having some behaviour of their own? You can track down stalled reification by looking for anaemic classes; classes which have a lot of accessors but very little behaviour. Once you’ve found an anaemic class, look for all the places that instances of it get used. Try moving some of the client code into methods on your anaemic class. Do that a few times and you’ll end up with a real object.
If you’re fussy about never putting HTML in your models, you could end up with a mediating builder/presenter object as well, but until you start wanting to render the same structured info in different formats, I’d suggest biting the bullet and living with HTML in the model as a lesser evil than structural code. Your mileage may vary.
Reading Beautiful Code
I’m reading Beautiful Code and it’s very good indeed. However, you have to feel sorry for Tim Bray – his chapter, “Finding Things” is excellent, as you’d expect. The only problem is, he’s following Jon Bentley, author of that perennial classic, Programming Pearls. It’s been a while since I (re)read anything by Bentley and I’d forgotten how good his prose was.
Which isn’t to say that Tim Bray is a bad writer. Far from it. It’s just that Jon Bentley is a very good one indeed.
Apart from that, Beautiful Code is a cracking book so far, and the royalties go to Amnesty International. You should buy a copy.
Five nouns for programmers 5
- Reification
- Notation
- Reticence
- Lucidity
- Intent
Learn ‘em. Live by them.
What did I miss? Which ones don’t belong on the list. What am I talking about?
