View
0
Download
0
Category
Preview:
Citation preview
Growing iOS Projects
Nicolas Seriot
CocoaHeads LausanneDecember 2011
Growing iOS ProjectsQuand un projet grandit, quand les spécifications s'étoffent, quand il s'agit de faire évoluer un logiciel ancien, l'estimation des tâches et leur réalisation peuvent devenir extrêmement difficiles. Le projet risque alors de s'enliser et de tomber dans le cercle vicieux des "quick fix", des bugs, des retards et de la baisse de motivation.
Cette présentation discute des principes, techniques et outils qui, au delà d'une bonne connaissance du SDK, permettent d'aborder les changements avec sérénité, d'écrire et surtout conserver un code en bon état, d'effectuer les modifications souhaitées dans les délais estimés.
La présentation traite spécifiquement des projets iOS et espère favoriser une discussion sur les façons de faire de chacun.
- for any developer or project manager- 45 - 60 minutes- difference between small and medium/large projects management- personal approach, what works for me
Complexity
t
- code gets dirtier with time- methods used get deprecated- changes pollute and break the original structure
Complexity
t
hell
- you lose the overall vision- chages become long and difficult- technical debt gets very expensive- estimates become random- fixing a bug yields two other bugs- the project is late- people lose motivation
Complexity
t
hellre-enginering
- avoid full rewrite- assert existing behaviour with unit tests- big refactoring
audit
hell
Complexity
t
re-enginering
- project gets unusable- audit to save what can be saved- list key components with their state- list dependancies- make priorities
Complexity
t
decisions
Taking the right decisions allow the project to remain manageable.
A clever person solves a problem.
A wise person avoids it.
- Albert Einstein
How Not to Get There
- but how to avoid troubles?
- keep a clean architecture- use the right tools- test- …
Simplicity
Simplicity is the ultimate sophistication.
- Leonardo da Vinci
Simplicity and elegance are unpopular because they require hard work and discipline to achieve and education to be appreciated.
- Dijkstra
- engineering principle- avoid getting into trouble- minimize costs and risks- not always easy- it’s not because of lazyness nor fear
Keep It Simple, Stupid
- developers often love over-engineerin- because we’re curious- because we love challenges
Good Developers
• know the tools
• know the frameworks
• know the business
• know people
• write good code…
- programming is ubiquitous in science and technology- anybody cannot be a good developer, though
1) Xcode, Instruments, App Store…2) don’t fight the framework, know good practices3) don’t work only for fame, but to fulfill the business needs3) able to anticipate changes, able to say NO4) knows what to expect from people
Good Code
• written for humans
• clean code allow precise estimates
• clean code is it own documentation
• good code in often aesthetic
• browse the framework for consistency
• messy code is costly (bugs, change, …)
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html
good code is good documentation to the reader
Bugs
• bugs should not be written in the first place
• read the code, errors, doc
• concentrate, minimize interruptions
• get a bug tracker
• debugging
• test an hypothesis
- good: start gdb and fix the bug- better: read the code and fix the bug- best: don’t write the bug
Revision Control Systems
• CVS, SVN, Git, Mercurial, …
• they still have to be used properly
• HEAD should always be ready for release
• commit messages, tags
• “leave the place cleaner than you found it”
Agility• ability to move quickly and easily
• best practices formalization, communication
• good process is not a substitute for good people
Success in software is 95% people, 5% process.
A team of highly skilled and highly motivated developers can create a successful project using almost any process... or even no process.
Software processes can enhance a skilled and motivated team. They can't do much if you don't have the talent or motivation in the organization.
Process
correct
clean
fast
start
unit tests refactoring
optimizations
- refactoring should be part of estimates- it’s not just “art for art”- minimize risks and costs- and most importantly keep a maintainable code base
• always check return status, early return
• always ask yourself: what could go wrong?
A good programmer is someone who looks both ways before crossing a one-way street.
- Doug Linder
Make it Robust
NSData *data = [NSData dataWithContentsOfFile:path];
NSError
NSError *error = nil;NSData *data = [NSData dataWithContentsOfFile:path options:0 error:&error];if(data == nil) { // deal with error return;}
- the file could be unreadable- the file could be on a very slow network- the file could be 2 GB- the file could be modified by another process
NSError Creation
NSString *errorDescription = NSLocalizedString(@"file cannot be read", @"");
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorDescription forKey:NSLocalizedDescriptionKey];
NSError *error = [NSError errorWithDomain:@"MyFileReader" code:0 userInfo:userInfo];
NSLog(@"-- %@", [error localizedDescription]);
Messaging nil// 1if(price && [price hasConsistentHighLow]) { // deal with price}
// 2if(prices) { for(Price *p in prices) { /* */ }}
// 3[prices addObject:p];
if(p) [prices addObject:p];
Comments
Good code is its own best documentation.
As you're about to add a comment, ask yourself, "How can I improve the code so that this comment isn't needed?"
- Steve McConnell, Code Complete
comments are substitutes for our incapacity to express ourselves in the code
// compute prices deltadouble delta = price.high - price.low;
// we need delta to ensure data is correctdouble delta = price.high - price.low;
BOOL priceHasConsistentHighLow = price.high >= price.low;
BOOL canSave = [price hasConsistentHighLow];
Comments
1. “what” -> bad2. “why” -> better3. good identifier -> better4. good code -> best
we replaced a bad comment with good code
Keep is Short
• long class > 300 lines
• long method > 15 lines
• minimize states (ivars)
• the goal is maintainability
code has to stay short to be maintainable
Documentation
• A “suitable qualified individual” should be able to understand what / where / why
• Wiki for co-workers
• text files / markdown for technical doc
- documentation should also be short- it’s more about environment- also project philosophy and architecture
Naming Things
Besides a mathematical inclination, a mastery of one's native tongue is the most vital asset of a competent programmer.
- Edsger Dijkstra
Methods Names
insert:at:
insertObject:atIndex:
viewWithTag:
taggedView:
- mutator methods start with a verb- accessor methods start with a nouns
- the last one is unclear because the parameter is not named
Avoid Generic Terms
• Factory, AbstractFactory
• Fooable interface
• Handler, Service
• Manager (except for singletons like NSFileManager, NSFontManager, …)
Unit Tests• document classes usage
• avoid regressions
• help in improving design
• see 2008 CocoaHeads presentation
• OCUnit should be enough
#ifdef UNIT_TESTS block();#else [queue addOperationWithBlock:block];#endif
Other Kind of Tests
• functional tests
• small scenarios (“user stories”)
• beta testers
• OTA distribution is really easy
- how else can you know that a code is a good code ?
• Working software is the primary measure of progress - Agile manifesto
• Maintenance burden, number of bugs
• Ultimate metrics
• money
• customer’s satisfaction
Metrics
One of my most productive days was throwing away 1,000 lines of code.
- Ken Thompson
Measuring programming progress by lines of code is like measuring aircraft building progress by weight. - Bill Gates
Lines of Code
• code is an liability, not an asset
• the real goal isto be maintainable before being small
- code is worth the business goal it fulfills- code has a cost, understanding and maintenance- removing code while keeping functionalities is adding value- but not to the detriment of clarity
$ find . -name "*.m" -print | xargs wc -l | sort
18 ./Classes/NSMutableArray+SQ.m 18 ./Classes/SQKeyValueObject.m 19 ./Classes/NSArray+SQ.m... 723 ./DynamicCharts/PricesContentsView.m 892 ./ManagedObjects/Quote.m 3059 ./ThirdParties/JSONKit/JSONKit.m 18098 total
several ways to count lines of code
$ cloc.pl . 17 text files. 17 unique files. 1 file ignored.
http://cloc.sourceforge.net v 1.55 T=1.0 s (16.0 files/s, 2839.0 lines/s)-------------------------------------------------------------------------------Language files blank comment code-------------------------------------------------------------------------------Objective C 9 533 361 1581C/C++ Header 7 102 132 130-------------------------------------------------------------------------------SUM: 16 635 493 1711-------------------------------------------------------------------------------
$ gnuplot loc.gp && open loc.png
http://quickies.seriot.ch/index.php?id=13
Third Party Libraries
• “don't reinvent the wheel”
• except when libraries are buggy…
• ASIHTTPRequest with iOS 5 (5+ kLoc)
• replaced with 300 Loc wrapper
Logging#ifdef DEBUG
#define MyLog(fmt, ...) NSLog((@"%@ [Line %d] %s " fmt), [self class], __LINE__, __FUNCTION__, ##__VA_ARGS__);
#define MyLogError(fmt, ...) NSLog((@"[Error] %@ [Line %d] %s " fmt), [self class], __LINE__, __FUNCTION__, ##__VA_ARGS__);
#else
#define MyLog(fmt, ...)
#define MyLogError(fmt, ...) NSLog((@"[Error] %@ [Line %d] %s " fmt), [self class], __LINE__, __FUNCTION__, ##__VA_ARGS__);
#endif
no need to import external classes
Code Coverage
- find and remove dead code- necessarily dynamic in Objective-C- .gcda and .gcno files
objc_cover.py
- (void)notUsed { return;}
- (void)actuallyUsed { return;}
// ...
[self actuallyUsed];
$ python objc_cover.py myBinaryFile # the following methods may be unreferenced-[MyClass notUsed]
Code Duplication
// MyClass.hextern NSString * const kMyString;
// MyClass.mNSString * const kMyString = @"xxx";
Code Duplication
$ find . -name "*.m" -print | xargs sort | uniq -c | sort
...
7 sl.backgroundColor = [UIColor clearColor].CGColor;
12 ![activityIndicator stopAnimating];
13 [ma addObject:[NSNull null]];
...
objc_strings.py• searches *.m and Localizable.strings
• compares entries
Concurrency• Always use main thread, except:
• lengthy computations, network access, …
• most of time use main thread for CoreData
- simplify your life every time it’s possible
- (void)dataWithCompletionBlock:(void (^)(NSData *data))completionBlock errorBlock:(void (^)(NSError *error))errorBlock {
NSOperationQueue *q = [[[NSOperationQueue alloc] init] autorelease];
[q addOperationWithBlock:^{ NSData *theData = nil; NSError *error = nil; // potentially slow access if(theData == nil) { errorBlock(error); return; } completionBlock(theData); }];}
[file dataWithCompletionBlock:^(NSData *data) { // handle data} errorBlock:^(NSError *error) { // handle error}];
Reduce Coupling
1. blocks, when possible (few methods)
2. delegates, good (couple of methods)
3. notifications
4. key-value observing (hard to debug)
Single Responsibility
The only way to write complex software that won’t fall on its face is to build it out of simple modules connected by well-defined interfaces, so that most problems are local and you can have some hope of fixing or optimizing a part without breaking the whole.
The Art of Unix Programming
objc_dep.py
• directed graph of #import directives
• inter-module dependencies analysis
• understand and improve architecture
• minimize coupling
• enforce MVC
$ objc_dep.py . > myproject.dotdigraph G {! node [shape=box];! "SearchVC" -> "AllClasses";! "SearchVC" -> "ClassStub";! "SearchVC" -> "ClassCell";! "HTTPRedirectResponse" -> "HTTPLogging";! "HTTPRedirectResponse" -> "HTTPResponse";! "InfoVC" -> "AppDelegate";! "ListTVC" -> "AllClasses";! "ListTVC" -> "ClassStub";! "ListTVC" -> "ClassDisplayVC";! "ListTVC" -> "ClassCell"; ...}
SearchVC
AllClasses
ClassStub
ClassCell
HTTPRedirectResponse
HTTPLogging HTTPResponse
InfoVC
AppDelegateListTVC
ClassDisplayVC
MethodCell
DDLog
DDASLLogger
ClassDisplay
FrameworkCell
HTTPDynamicFileResponse
HTTPAsyncFileResponse
HTTPConnection
HTTPAuthenticationRequest
HTTPMessage
HTTPDataResponse
GCDAsyncSocket
TreeTVC
DDRange
DDNumber
main
HTTPServer
WebSocket
DDData
FrameworksTVC
ObjectsTVC
MyIP
DDTTYLogger
HTTPFileResponse
DDFileLogger
RuntimeBrowser_Prefix
- blue: double imports- red: imports from .pch
SearchVC
AllClasses
ClassStub
ClassCell
InfoVC
AppDelegateListTVC
ClassDisplayVC
MethodCellClassDisplay
FrameworkCell
TreeTVC main
CocoaHTTPServer
FrameworksTVC
ObjectsTVC
RuntimeBrowser_Prefix
MVC
separate logic from controllers
SearchVC
AllClassesClassStub
ClassCell
InfoVC
AppDelegate
ListTVC
ClassDisplayVC
MethodCell
ClassDisplay
FrameworkCell
TreeTVC
CocoaHTTPServer
FrameworksTVC
ObjectsTVC
Views
Controllers
NetworkRuntime Browser Logic
- double import- AppDelegate has way too much responsibilities
Runtime Browser Logic
ClassStubClassDisplay
BrowserCell
BrowserNodeAllClasses
AppControllerNSTextView+SyntaxColoring
Controller
View
- BrowserCell should not import BrowserNode
# number of imports
0 | *********** 1 | ********** 2 | *** 3 | ***** 4 | *** 5 | ** 6 | 7 | 8 | 9 | *10 | 11 | 12 | 13 | *
...
9 | AppDelegate13 | HTTPConnection
# times imported
0 | 1 | ********* 2 | ***** 3 | ****** 4 | ** 5 | *** 6 | 7 | * 8 | *
...
7 | HTTPResponse 8 | HTTPLogging
bonus: stats on stderr
Network
Mac OS X Views
iPhone Controllers
Runtime Browser Logic
Mac OS X Controllers
iPhone Views
Foundation
AppKit UIKit
Recap
• simplicity principle
• tools and techniques
• clear & simple code, design, communication
• make good software
Conclusion
• frustration is part of the process
• comp. prog. is a craft, a science and an art
• no silver bullet
• sharing ideas which may help in finding solutions
Yes, this is hard. No, you are not stupid.
Aaron Hillegass
… and your little plant …
Recommended