This post has been automatically generated. I use this blog to collect links that I have bookmarked. All activity is automated.
Don’t start with an Application, start with the API. This might seem weird, but it actually makes a lot of sense, because the API defines how other computer things can interact with the data your application manipulates. But hey, why should we leave that as “other computer things”? Let me rephrase:
The API defines how your application (and other computer things) can interact with the data you’re interested in.
My usual design approach is to examine the inputs and outputs of the application, and then design an API that handles such data. Later I’ll stack the user experience and the “glue” code on top of such API to form an actual application.
This fits nicely with the “One Module Does One Thing” approach because then the API defines which kind of data you’re dealing with, and all the meaningful transformations you can apply to such data. Transformations in turn do one thing, and might be grouped logically. Modules are really just a logical group of computations, that it happens to be a file in many languages is just an accident.
For example, let’s say we want to write an application to display Tweets to the user. First and foremost, we examine the inputs and outputs, and draft some types to encode the data in our application (I’m using a type notation inspired by Haskell, but this should be nonetheless pretty straightforward):
-- | A TwitterTweet is something Twitter gives us (the input) -- (note that this is a stripped down version) type TwitterTweet favorited :: Boolean retweeted :: Boolean retweet_count :: Number created_at :: String -- ^ Serialised Date source :: String user :: User id_str :: String in_reply_to_user_id_str :: String in_reply_to_status_id_str :: String in_reply_to_screen_name :: String entities :: { "urls" :: [Entity] , "hashtags" :: [Entity] , "user_mentions" :: [Entity] } text :: String
So, the data Twitter gives us is quite a mess, and it’d be really difficult to manipulate that kind of data in our application. We can do better, so let’s define a better type to encode a Tweet:
type User name :: String -- ^ The users' screen name id :: String -- ^ Twitter's user ID type Entity url :: String expandedUrl :: String displayUrl :: String indices :: (Number, Number) type Text plain :: String entities :: { "urls" :: [Entity] , "hashtags" :: [Entity] , "user_mentions" :: [Entity] } type Tweet id :: String -- ^ Unique identifier for this tweet user :: User -- ^ The user that tweeted this inReplyTo :: Tweet source :: String -- ^ Which application was used to compose this date :: Date -- ^ When this tweet was crafted favourited :: Boolean retweeted :: Boolean retweetCount :: Number text :: Text
So, now User
and Text
are separate types, this is because they make sense outside of the context of a Tweet
and we might want to manipulate them separately. There’s no reason to provide a complected type to a function that only needs to know the name of a user, for example.
Once we’re done with the types our application needs to manipulate, we can draft an API that provides the primitives to manipulate these types, given the operations we’ll be applying to them and the output.
-- * Type conversions -- | We need to convert from Twitter format to ours normaliseTweet :: TwitterTweet -> Tweet -- | Convert Twitter Date serialisation to actual DateTime parseDate :: String -> Date -- | Extract the User that composed the tweet twittedBy :: TwitterText -> User -- | Extract reply information repliedToUser :: TwitterText -> User repliedToTweet :: TwitterText -> Tweet -- | Extract the Text textFor :: TwitterText -> Text -- * Display transformations -- (These are application-specific because they only make sense in the -- context of user-facing HTML pages) -- | We want to display a Tweet as HTML renderTweet :: Tweet -> HTML -- | We want to display a Text as HTML textToHTML :: Text -> HTML -- | We want to know the relative time since the tweet fromNow :: Date -> String -- | We want to display a link to a User linkToUser :: User -> HTML -- | We also want to display a link to a Tweet linkToTweet :: Tweet -> HTML
If there’s one hint I can provide when doing the initial API design, it would be:
Extract the most basic, orthogonal concepts, and provide primitives to encode them.
You can always add combinators on top of those minimal and simple primitives to let them do more stuff. Working with reeeeally small set of primitives and a lot of combinators means you get to write simple code that actually scales! But then, picking the right primitives can be really hard at times, so you need to have a good deal of knowledge about the domain you’re trying to encode in your API.
via Echo JS http://killdream.github.io/blog/2013/04/the-hikkikomoris-guide-to-javascript/