Upload
francois-zaninotto
View
10.257
Download
0
Embed Size (px)
DESCRIPTION
Usability Applied to Programming
Citation preview
Developing for DevelopersUsability Applied to Programming
I’ll talk about development for an open-source project / library / plugin - meaning, for other developers. That usually means you’re not paid, you have no commitment on milestones and you are look for fame/love/hate from your peers. In this case, don’t do the exact opposite as when you are paid...Keep usability in mind and mind the developers who will use your work.I’ll focus on applying usability principles to API design, and illustrate by the work I recently done on a symfony plugin
Developing for End-UsersPrototypeUser testingRequirementsSchedulingCodeAPI DocumentationUnit testingUser manualPerformance tweakingPackagingCommunicationSupport
To begin, let’s see what happens when you do not develop directly for developers, but for end-users. That happens if you’re a software editor, or if you’re offering an, internet service for instance.Here is a simplified list of the things you should do to achieve a development for end-users
Developing for a CustomerPrototypeUser testingRequirementsSchedulingCodeAPI DocumentationUnit testingUser manualPerformance tweakingPackagingCommunicationSupport
Packaging and communication are handled by the customerNow, what happens most of the time when someone develops for developers?
Developing for DevelopersPrototypeUser testingRequirementsSchedulingCodeAPI DocumentationUnit testingUser manualPerformance tweakingPackagingCommunicationSupport
Most of the time, everything is forgotten. When they’re developing for their peers, developers often omit all but the code This makes it difficult for other developers to use this piece of work.And the most important is: no user manual
Think “Product”
A library/Plugin deserves the same kind of attention you would give to shaping and creating the next iPod. It may not make you the richest person in the world, but it’s not an excuse to work like an amateur.
Where to Start1. Tease
2. Work on user manual
3. Write unit tests
4. Write code
5. Release early
6. Communicate
7. Use it yourself
8. Take user feedback into account
9. Go to 1. and start again
Here is a simple process I suggest for developments targeted to developers.It will not take your entire time, but will bring you huge benefitsSince you write Documentation first, I will call this process “Documentation-Driven Development”
Where to Start1. Tease
2. Work on user manual
3. Write unit tests
4. Write code
5. Release early
6. Communicate
7. Use it yourself
8. Take user feedback into account
9. Go to 1. and start again
Test-DrivenDevelopment
Here is a simple process I suggest for developments targeted to developers.It will not take your entire time, but will bring you huge benefitsSince you write Documentation first, I will call this process “Documentation-Driven Development”
Where to Start1. Tease
2. Work on user manual
3. Write unit tests
4. Write code
5. Release early
6. Communicate
7. Use it yourself
8. Take user feedback into account
9. Go to 1. and start again
Documentation-DrivenDevelopment
Here is a simple process I suggest for developments targeted to developers.It will not take your entire time, but will bring you huge benefitsSince you write Documentation first, I will call this process “Documentation-Driven Development”
Doing ItBetter chance to get higher adoption
Better chance to get contributors
Greater code quality
Easier Improvements
Less time spent on support since the documentation is better
More fun
Not Doing ItLess work
Faster?
Good way to have only the true developers using your stuff
Better chance to do something nobody will care about
Pros and ConsFaster? Not in the long runSo let’s illustrate this process throughout a practical example
DbFinderDbFinder is a usability layer built on top of Propel, that can also be used with Doctrine if you want. It makes retrieving model objects very easy, much easier anyway than what you’re used to do with Propel Criteria objects and Peer classes.I applied the DDD process to the DbFinder development, so it’s a good illustration
1. Tease
Create expectationsAlso, for an efficient teasing, you need an efficient name, so name your library wisely (not sfPropelImpersonatorPlugin)
1. Tease
“DbFinder is like jQuery for Propel”
Create expectationsAlso, for an efficient teasing, you need an efficient name, so name your library wisely (not sfPropelImpersonatorPlugin)
1. Tease
“DbFinder is like jQuery for Propel”
“DbFinder allows for ORM agnostic plugins”
Create expectationsAlso, for an efficient teasing, you need an efficient name, so name your library wisely (not sfPropelImpersonatorPlugin)
1. Tease
“DbFinder is like jQuery for Propel”
“DbFinder allows for ORM agnostic plugins”
“DbFinder: The ORM isn’t important anymore”
Create expectationsAlso, for an efficient teasing, you need an efficient name, so name your library wisely (not sfPropelImpersonatorPlugin)
1. Tease
“DbFinder is like jQuery for Propel”
“DbFinder allows for ORM agnostic plugins”
“DbFinder: The ORM isn’t important anymore”
“DbFinder makes you sexy”
Create expectationsAlso, for an efficient teasing, you need an efficient name, so name your library wisely (not sfPropelImpersonatorPlugin)
1. Tease
“DbFinder is like jQuery for Propel”
“DbFinder allows for ORM agnostic plugins”
“DbFinder: The ORM isn’t important anymore”
“DbFinder makes you sexy”
“Enlarge your Penis with DbFinder!”
Create expectationsAlso, for an efficient teasing, you need an efficient name, so name your library wisely (not sfPropelImpersonatorPlugin)
2. Work on User ManualKeep usability in mind
Linear, not hypertext
Well written, with no errors and up to date
Must be enough for beginner to intermediate (80% needs)
Let the API documentation explain the advanced
Designed as “a pleasant journey from ignorance to knowledge”
K.I.S.S., D.R.Y.
How do you write a user manual when you have no code? It’s similar to writing requirements, only you go directly to the API.Dry is for the doc, the API as well as for the codeAlso, a few more requirements for the DbFinder user manual specificallyNow, you’ll see through this user manual how to apply usability principles to Development
2. Work on User ManualKeep usability in mind
Linear, not hypertext
Well written, with no errors and up to date
Must be enough for beginner to intermediate (80% needs)
Let the API documentation explain the advanced
Designed as “a pleasant journey from ignorance to knowledge”
K.I.S.S., D.R.Y.Must be faster to read than Criteria/Peer stuff
Must not require prior knowledge of Criteria/Peer stuff
How do you write a user manual when you have no code? It’s similar to writing requirements, only you go directly to the API.Dry is for the doc, the API as well as for the codeAlso, a few more requirements for the DbFinder user manual specificallyNow, you’ll see through this user manual how to apply usability principles to Development
Not DDD
$c = new Criteria();$c->add(ArticlePeer::TITLE, '%world', Criteria::LIKE);$c->add(ArticlePeer::IS_PUBLISHED, true);$c->addAscendingOrderByColumn(ArticlePeer::CREATED_AT);$articles = ArticlePeer::doSelectJoinCategory($c);
Here is an example of what you should not do. It is the current Propel query API.What would you need to document to explain how to use Propel’s Peer classes and Criteria stuff?Request a list of Article objects, based on a word in the title and a publication status, and hydrate the related category model object at the same timeBesides, it’s all in the wrong order: you know thet you receive articles at the end...
Not DDD
$c = new Criteria();$c->add(ArticlePeer::TITLE, '%world', Criteria::LIKE);$c->add(ArticlePeer::IS_PUBLISHED, true);$c->addAscendingOrderByColumn(ArticlePeer::CREATED_AT);$articles = ArticlePeer::doSelectJoinCategory($c);
Here is an example of what you should not do. It is the current Propel query API.What would you need to document to explain how to use Propel’s Peer classes and Criteria stuff?Request a list of Article objects, based on a word in the title and a publication status, and hydrate the related category model object at the same timeBesides, it’s all in the wrong order: you know thet you receive articles at the end...
Not DDD
$c = new Criteria();$c->add(ArticlePeer::TITLE, '%world', Criteria::LIKE);$c->add(ArticlePeer::IS_PUBLISHED, true);$c->addAscendingOrderByColumn(ArticlePeer::CREATED_AT);$articles = ArticlePeer::doSelectJoinCategory($c);
Here is an example of what you should not do. It is the current Propel query API.What would you need to document to explain how to use Propel’s Peer classes and Criteria stuff?Request a list of Article objects, based on a word in the title and a publication status, and hydrate the related category model object at the same timeBesides, it’s all in the wrong order: you know thet you receive articles at the end...
Not DDD
$c = new Criteria();$c->add(ArticlePeer::TITLE, '%world', Criteria::LIKE);$c->add(ArticlePeer::IS_PUBLISHED, true);$c->addAscendingOrderByColumn(ArticlePeer::CREATED_AT);$articles = ArticlePeer::doSelectJoinCategory($c);
Here is an example of what you should not do. It is the current Propel query API.What would you need to document to explain how to use Propel’s Peer classes and Criteria stuff?Request a list of Article objects, based on a word in the title and a publication status, and hydrate the related category model object at the same timeBesides, it’s all in the wrong order: you know thet you receive articles at the end...
Not DDD
$c = new Criteria();$c->add(ArticlePeer::TITLE, '%world', Criteria::LIKE);$c->add(ArticlePeer::IS_PUBLISHED, true);$c->addAscendingOrderByColumn(ArticlePeer::CREATED_AT);$articles = ArticlePeer::doSelectJoinCategory($c);
Here is an example of what you should not do. It is the current Propel query API.What would you need to document to explain how to use Propel’s Peer classes and Criteria stuff?Request a list of Article objects, based on a word in the title and a publication status, and hydrate the related category model object at the same timeBesides, it’s all in the wrong order: you know thet you receive articles at the end...
Not DDD
$c = new Criteria();$c->add(ArticlePeer::TITLE, '%world', Criteria::LIKE);$c->add(ArticlePeer::IS_PUBLISHED, true);$c->addAscendingOrderByColumn(ArticlePeer::CREATED_AT);$articles = ArticlePeer::doSelectJoinCategory($c);
Here is an example of what you should not do. It is the current Propel query API.What would you need to document to explain how to use Propel’s Peer classes and Criteria stuff?Request a list of Article objects, based on a word in the title and a publication status, and hydrate the related category model object at the same timeBesides, it’s all in the wrong order: you know thet you receive articles at the end...
Not DDD
$c = new Criteria();$c->add(ArticlePeer::TITLE, '%world', Criteria::LIKE);$c->add(ArticlePeer::IS_PUBLISHED, true);$c->addAscendingOrderByColumn(ArticlePeer::CREATED_AT);$articles = ArticlePeer::doSelectJoinCategory($c);
Here is an example of what you should not do. It is the current Propel query API.What would you need to document to explain how to use Propel’s Peer classes and Criteria stuff?Request a list of Article objects, based on a word in the title and a publication status, and hydrate the related category model object at the same timeBesides, it’s all in the wrong order: you know thet you receive articles at the end...
Rely on Current Knowledge
English
SQL
sfBrowser / lime_test
ActiveRecord in other languages
To document a new lib, and to design its API, you must rely n current developer knowledgeknowledge comes from practice / habiteases learning a great dealdo not reinvent the wheel
Keep Number of Public Objects and Methods Low
Every new object is something to explain. To make it simple, reduce the number of objects directly accessible to the end user. This doesn’t prevent you from using more objects under the hood, but it reduces short-term memory load.
Keep Number of Public Objects and Methods Low
DbFinder
Every new object is something to explain. To make it simple, reduce the number of objects directly accessible to the end user. This doesn’t prevent you from using more objects under the hood, but it reduces short-term memory load.
Keep Names Short and Easy to Remember
from()
where()
orderBy()
with()
find()
Compare with addAscendingOrderByColumn(), doSelectJoinXXX(), ...Also helps coding faster (less code to type)
Be Consistent
find()
findPk()
findOne()
findByTitle()
findOneByTitle()
CamelCase, verbs/nouns, no different verbs (retrieve/select/fetch/find/execute)Eases memorizationBonus: in similar situations, users “infer” the API thanks to its consistency
Design Self-Explanatory API
$articles = DbFinder::from('Article')-> where('Title', 'like', '%world')-> where('IsPublished', true)-> orderBy('CreatedAt')-> with('Category')-> find();
Can somebody explain me what this piece of code is doing (preferably, someone who never ever saw DbFinder code) ?Notice how the first thing in this statement is the declaration of what we look for (it’s the last thing in a usual Propel Query).All the code is pretty much self-explanatory and looks familiar to SQL devsSo seeing some real code becomes somehow a manual. Every application using a well-designed API adds up to the amount of available doc (think askeet, snipeet, etc)
Group Sequences of Actions
$articles = DbFinder::from('Article')-> where('Title', 'like', '%world')-> where('IsPublished', true)-> orderBy('CreatedAt')-> with('Category')-> find();
Provide a beginning and an end (Initialization method, Termination method)cf: if/endif, foreach/endforeach, etc.Helps organizing code into blocks (more readable, easier to identify)in between: filters, in the flow. Code indentation also helps to identify a sequence
Cover All Use Cases$articleFinder = DbFinder::from('Article');// Finding all Articles where title = 'foo'$articles = $articleFinder-> where('Title', 'foo')-> find();// Finding all Articles where title like 'foo%'$articles = $articleFinder-> where('Title', 'like', 'foo%')-> find();// Finding all Articles where published_at less than time()$articles = $articleFinder-> where('PublishedAt', '<', time())-> find();
The API should not penalize the features, so don’t simplify too much, or you’ll limit what the library can do
Cover all Use Cases (2)
// Finding all Articles// Where title is ‘Foo’ or ‘Bar’$article = DbFinder::from('Article')-> where('Title', '=', 'Foo', 'cond1')-> where('Title', '=', 'Bar', 'cond2')-> combine(array('cond1', 'cond2'), 'or')-> findOne();
You probably need to list all use cases before starting your API (that’s the professional way)If you don’t cover 80% of the use cases and allow the remaining 20% to be implemented easily, the library won’t be used
Offer Shorcuts and Proxy Methods
// Finding all Articles$articles = DbFinder::from('Article')->find();// Finding 3 Articles$articles = DbFinder::from('Article')->find(3);// Finding a single Article$article = DbFinder::from('Article')->findOne();// Finding the last Article // And figure out the column to use for sorting)$article = DbFinder::from('Article')->findLast();
Power users need shorter way to do common stuff. This increases the pace of programming, at the risk of bloating the library. Do it wisely.Watch out: TIMTOWDI is wrong
Offer Clear BenefitOver the Existing
$pager = DbFinder::from('Article')-> where('Author.Nickname', $nickname)-> with('I18n', 'Category')-> paginate($currentPage = 1, $maxResultsPerPage = 10);
Easyness of use is a clear benefit of DbFinder over PropelThat’s even a benefit over Doctrine!
Offer Clear BenefitOver the Existing (2)
class ArticleFinder extends DbFinder{ protected $class = 'Article'; public function recent() { $this->where('CreatedAt', '<', time() - 86400); return $this; }}
$articles = DbFinder::from('Article')-> recent()-> find();
Better code readability: the finder ‘filters’ results
Simple != Not Powerful
$article = DbFinder::from('Article')-> where('Title', 'like', 'foo%')-> addOr(ArticlePeer::TITLE, 'bar%', Criteria::LIKE)-> findOne();
Simple features appeal newcomersPowerful features make them stayYou must target both to get an increasing user base
Simple != Not Powerful
$article = DbFinder::from('Article')-> where('Title', 'like', 'foo%')-> addOr(ArticlePeer::TITLE, 'bar%', Criteria::LIKE)-> findOne();
Simple features appeal newcomersPowerful features make them stayYou must target both to get an increasing user base
Use MagicBut Not Too Much
$article = DbFinder::from('Article')-> whereIsPublished()-> orderByCreatedAt()-> withCategory()-> findOne();
I primarily introduced more magic, but recently made it less proeminent or removed it completelyThe developer must be able to understand how the magic works to avoid acting blindly and making mistakesEclipse should be able to provide code hints
Use MagicBut Not Too Much
$article = DbFinder::from('Article')-> whereIsPublished()-> orderByCreatedAt()-> withCategory()-> findOne();
I primarily introduced more magic, but recently made it less proeminent or removed it completelyThe developer must be able to understand how the magic works to avoid acting blindly and making mistakesEclipse should be able to provide code hints
Use Real World Examples
// Retrieving an article and its # of comments// In a single query$article = DbFinder::from('Article')-> join('Comment')-> groupBy('Article.Id')-> withColumn('COUNT(Comment.Id)', 'NbComments')-> findOne();echo $article->getColumn('NbComments');
The user manual could almost be reduced to a set of code samplesAfter all, you’re addressing developers, not literature fansDbFinder README file is 90% code
Borrow Ideas
jQuery
Doctrine
Rails has_finder
SQLAlchemy
You’re not the smartest person in the world
3. Write Unit Tests
4. Write Code
You know all that stuff. That’s your everyday job, that’s what you do best.Your unit tests can (should) use the same examples as your user manualSo they’re already half-written
Handle Errors KindlyHelp Debugging
Unique piece of usability than you can’t really plan when writing the user manualTyped exception definitely need to be implemented in DbDinder, to allow to catch them specifically
Handle Errors KindlyHelp Debugging
Control methods input
Unique piece of usability than you can’t really plan when writing the user manualTyped exception definitely need to be implemented in DbDinder, to allow to catch them specifically
Handle Errors KindlyHelp Debugging
Control methods input
Provide helpful Exceptions
Unique piece of usability than you can’t really plan when writing the user manualTyped exception definitely need to be implemented in DbDinder, to allow to catch them specifically
Handle Errors KindlyHelp Debugging
Control methods input
Provide helpful Exceptions
Log information
Unique piece of usability than you can’t really plan when writing the user manualTyped exception definitely need to be implemented in DbDinder, to allow to catch them specifically
Handle Errors KindlyHelp Debugging
Control methods input
Provide helpful Exceptions
Log information
Provide debugging tools
Unique piece of usability than you can’t really plan when writing the user manualTyped exception definitely need to be implemented in DbDinder, to allow to catch them specifically
Handle Errors KindlyHelp Debugging
Control methods input
Provide helpful Exceptions
Log information
Provide debugging tools
Automate initialization tasks
Unique piece of usability than you can’t really plan when writing the user manualTyped exception definitely need to be implemented in DbDinder, to allow to catch them specifically
Handle Errors KindlyHelp Debugging
Control methods input
Provide helpful Exceptions
Log information
Provide debugging tools
Automate initialization tasks
Avoid using private methods/properties
Unique piece of usability than you can’t really plan when writing the user manualTyped exception definitely need to be implemented in DbDinder, to allow to catch them specifically
5. Release Early (Release Often)
March April June July August September
March 27th - 0.1.0 Alpha•Basic proof of concept•Suitable only for communication•Magic
March 1stStart
March 31st - 0.2.0 Beta• Implementation of most major features• Shortcuts and proxy methods
July 7th - 0.3.0 Beta• Advanced features•Complex use cases•Less magic•Refactoring•Bug fixes•Almost usable for test purpose
August 12th - 0.4.0 Beta•Refactoring•API consolidation•Doctrine adapter•Bug fixes
August 28th - 0.9.0 Beta•Refactoring•Bug fixes•Performance enhancements•100% Doctrine Implementation•Suitable for production
All along, 100% user manual for larger adoption and 100% unit test coverage for regression safety
6. CommunicateBlog Posts
Forums
Mailing-lists
IRC / Tweets
Friends
Word to Mouth
Reply to comments
Screen captures / Screencasts
Showcase apps / Demo
Conferences
Don’t be deceptive
Be enthusiast
Invite others
7. Use It YouselfTest it for real. You’ll see what’s not necessary and what’s missing, what’s not practical and how you can improve usability. Even better, if you can: have someone else use the lib (=user testing)sfSimpleBlogPlugin uses DbFinder. It is a living user manual and a real-scale test app
1. Tease
2. Work on user manual
3. Write unit tests
4. Write code
5. Release early
6. Communicate
7. Use it yourself
8. Take user feedback into account
9. Go to 1. and start again
I’m all ears
Thank you(we’re recruiting)