❗ DRAFT ❗
Guida di riferimento per i progetti Objective-C in Tiknil. Non vuole essere l'ennesima riproposizione dello stile di stesura dei progetti in questo linguaggio, ma uno strumento utile per il team e i suoi collaboratori.
Sentitevi liberi di dissentire da quanto abbiamo deciso di tenere come stile guida! 😉
Troppo lunga da leggere? E' solo l'ennesima guida di stile di Obj-C? Ok, passiamo al dunque, nerd: usa i tool che ti elenchiamo per cominciare a 'subire' un po' di codice di qualità:
-
XCode snippets - dovresti leggere perché usarli, almeno
-
Installa
BBUncrustifyPlugin
tramite Alcatraz e usa il fileuncrustify.cfg
che trovi nel presente repo. Tranquilli, è tutto ben descritto in Tools.
Di seguito le linee guida che abbiamo consultato e a cui facciamo riferimento per la stesura di questo documento:
- Apple coding guidelines
- Ray Wenderlich Obj-C style guide
- NY Times Obj-C style guide
- GitHub Obj-C style guide
- Google Obj-C style guide
Perché abbiamo preso certe scelte e non altre? Ecco i concetti che guidano alcune scelte esposte in questa guida (in ordine non per forza di priorità):
- Bellezza e stile uniforme, anche nel codice
- Comprensibilità del codice da chiunque
- Velocità di scrittura del codice
- Produzione della documentazione in Italiano
- Somiglianze con altri linguaggi che utilizziamo per i progetti
- Abitudini nostre (in via di miglioramento)
Usare la lingua Inglese per il codice, quella Italiana per i commenti e la documentazione del codice (ove non espressamente richiesta la lingua inglese)
👍 UIColor *myColor = [UIColor whiteColor];
👎 UIColor *mioColore = [UIColor whiteColor];
Raccomandato l'uso dei #pragma mark
per raggruppare i metodi in gruppi funzionali e legati all'implementazione dei protocolli/delegati seguendo la struttura seguente (da RW Obj-C style guide):
#pragma mark - Class methods
+ (instancetype) shared;
#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - IBActions
- (IBAction)submitData:(id)sender {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - Private
- (void)privateMethod {}
#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}
Per semplificare l'utilizzo dell'organizzazione del codice come descritto consigliamo di utilizzare lo snippet di XCode apposito come descritto nel repo Xcode-snippets: basta digitare def
quando si sta per stendere l'implementazione di una nuova classe.
Per scrivere codice di qualità i commenti sono fondamentali: essi rientrano nei requisiti non funzionali o di qualità (ISO IEC 9126) di tutti i progetti sofware all'interno della voce "Manutenibilità".
- I commenti, quando necessari, devono spiegare perché una particolare parte di codice fa qualcosa. Ogni commento che è utilizzato dev'essere sempre aggiornato o eliminato.
- Preferire codice auto-esplicativo (dando nomi significativi alle variabili e ai metodi, vedi Naming, se possibile, rispetto ai commenti. Nel dubbio, metterli entrambi.
Come commentare? Ecco un ottimo (e breve) articolo su NSHipster relativo alla documentazione Obj-C Documentation
/**
Questo è un commento
*/
e
/**
Questo è il commento alla dichiarazione di questo metodo che ha come parametro paramValue e che ritorna come risultato resultValue
@param paramValue il parametro passato a questo metodo
@result resultValue il risultato che viene ritornato x o y in base al parametro
*/
Non sei sicuro di riuscire a ricordarti sempre come scrivere i commenti? Fatti aiutare dagli snippets com
...!
Fare riferimento alle linee guida Apple riprese anche da RW per cui:
I nomi dei metodi e delle variabili devono essere descrittivi, va bene anche se sono lunghi
👍 UIButton *settingsButton;
👎 UIButton *setBut;
Le costanti devono essere camel-case con tutte le parole con la prima lettera maiuscola e devono iniziare con il nome della classe a cui fanno riferimento (se lo fanno).
👍 static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;
👎 static NSTimeInterval const fadetime = 1.7;
I campi (@property
) delle classi devono essere camel-case con la prima lettera minuscola. Preferire l'auto-sintesi dei campi piuttosto che scrivere manualmente i @synthesize
a meno che ci sia una buona ragione.
👍 @property (strong, nonatomic) NSString *descriptiveVariableName;
👎 id varnm;
I nomi dei metodi devono essere descrittivi, come già detto nel paragrafo precedente. I parametri formali del metodo devono essere separati da uno spazio (come da stile Apple). Aggiungere sempre un nome per il parametro e descrivere a cosa serve quel parametro. Non usare le parole 'and' e 'with' o similari, come descritto in questi esempi:
👍
- (void) setExampleText:(NSString *)text image:(UIImage *)image;
- (void) sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id) viewWithTag:(NSInteger)tag;
- (instancetype) initWithWidth:(CGFloat)width height:(CGFloat)height;
👎
- (void) setT:(NSString *)text i:(UIImage *)image;
- (void) sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- (id) taggedView:(NSInteger)tag;
- (instancetype) initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype) initWith:(int)width and:(int)height; // Never do this.
Le variabili devono essere il più descrittive possibile. L'uso di variabili con una sola lettera è ammesso solo per i cicli for
.
Dove si mette l'asterisco per le variabili che puntano ad un oggetto?
👍 NSString *text
👎 NSString* text
or NSString * text
(tranne che per le costanti)
Si preferisce l'uso delle @property
private piuttosto che d'istanza. Per @property
private si intende quelle con definizione nel file .m
in una categoria detta anonima (indicata dal fatto che è descritta con ()
):
interface RWTDetailViewController ()
@property (strong, nonatomic) GADBannerView *googleAdView;
@property (strong, nonatomic) ADBannerView *iAdView;
@property (strong, nonatomic) UIWebView *adXWebView;
@end
Si preferisce usare le @property
private piuttosto che i campi d'istanza. Questo vale anche per i campi IBOutlet
generati (o predisposti) da drag&drop a partire da Interface Builder: di default vanno messi come @property
privata nel file .m
; qualora sia necessario averli pubblici, allora verranno spostati nel .h
.
👍
@interface RWTTutorial : NSObject
@property (strong, nonatomic) NSString *tutorialName;
@end
👎
@interface RWTTutorial : NSObject {
NSString *tutorialName;
}
Come descritto in Underscores si preferisce non accedere direttamente alle @property
se non nei metodi di 'Lifecicle' dell'oggetto (init
, dealloc
, etc) e nei 'custom accessors'.
Gli attributi delle property devono essere scritti perché servono ed aiutano chi legge il codice a comprenderlo meglio. L'ordine degli attributi deve essere: storage > atomicity così da essere coerente con il codice generato da Interface Builder quando si trascinano i collegamenti agli elementi UI.
👍
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) NSString *tutorialName;
👎
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic) NSString *tutorialName;
Preferire strong
a retain
(che sono la stessa cosa: SO answer - Apple Doc) per migliore formattazione del codice
Usare sempre weak
per gli oggetti IBOutlet
. Ti chiedi perché? Fattelo spiegare da NSHipster e dalla Resource Programming Guide section on Nib Files di Apple.
La RW Obj-C style guide suggerisce di utilizzare copy
piuttosto di strong
per avere la certezza che la @property
non venga mutata una volta assegnata. Chiaramente dipende dal contesto, quindi valutate di conseguenza
Quando si usano i campi (@property
) d'istanza essi devono essere sempre richiamati usando self.
. Questo rende più evidente in maniera visiva l'utilizzo dei campi d'istanza.
Fa eccezione l'utilizzo dei campi con underscore (_variableName
) nei metodi init
o nei metodi getter/setter che ne richiedano l'utilizzo per il corretto funzionamento.
Le variabili locali non devono contenere underscore.
Le categories devono avere nomi che ne definiscano la funzionalità. Attenzione a non creare categories che fanno uso di altre categories (il debug potrebbe diventare arduo).
👍 @interface NSString (StringEncodingDetection)
👎 @interface NSString (Utilities)
I metodi delle category devono avere sempre il prefisso seguito da underscore. Questo limita la possibilità di creare eventuali duplicati di metodi esistenti tra le varie librerie.
- (NSStringEncoding) sed_detectStringEncoding:(NSString*)string;
Se hai necessità di esporre dei metodi privati per delle sottoclassi o per fare test crea una categories chiamata Class+Private
Preferire sempre l'uso dei literals piuttosto delle descrizioni estese per gli oggetti del framework, in particolare NSString
, NSDictionary
, NSArray
e NSNumber
:
👍
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @10018;
👎
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingStreetNumber = [NSNumber numberWithInteger:10018];
Per noi è importante trovare i tool che ci permettano di mantenere certe scelte in modo costante e coerente di progetto in progetto. Partecipando ad un interessante talk di Anastasia Kazakova (@anastasiak2512) alla #Pragma conf 2015 a Firenze abbiamo visto che IDE come AppCode integrano strumenti per la formattazione del codice in modo avanzato ma tramite alcuni plugin e risorse è possibile avere queste funzionalità anche su XCode.
Quello che abbiamo trovato più completo e facile da capire è Uncrustify che in XCode è facilmente intergrabile tramite il plugin BBUncrustifyPlugin, installabile anch'esso tramite Alcatraz.
Una volta installato basta andare in Edit > Format Code > BBUncrustifyPlugin preferences
, scegliere come formatter Uncrustify
e alla voce Clang style
scegliere Custom Style (File)
(se lo desiderate, altrimenti scegliete il formattatore che più vi aggrada).
Alla voce Configuration File
dunque scegliere Create Configuration File
e il vostro editor di testo preferito (sarà indubbiamente Sublime Text.
Per utilizzare lo stile delineato in questa guida basta prendere il file uncrustify.cfg
e copiarlo in una qualsiasi cartella padre della cartella del progetto di XCode che avete aperto. Il consiglio è di avere il file uncrustify.cfg
nella cartella root dei vostri progetti iOS/OSX.
Se volete fare le cose per bene, fate così:
-
Fate un fork di questo repository e scaricatelo in locale nella cartella
$OBJ_C_STYLE_GUIDE_REPO
-
Quindi create un link simbolico al file
uncrustify.cfg
nella cartella root dei vostri progetti iOS/OSX
ln -s $OBJ_C_STYLE_GUIDE_REPO/uncrustify.cfg $IOS_OSX_PROJECTS_ROOT/uncrustify.cfg
Per formattare un file o le righe selezionate in XCode tramite Uncrustify basterà quindi selezionare Edit > Format Code >
e scegliere Format Selected Files
, Format Active File
o Format Selected Lines
.
Puoi impostare gli shortcut da tastiera semplicemente nell'app Preferences
di sistema: