[iOS Dev] Objective-C Style Guide
สิ่งสำคัญเวลาจะเขียน Code ภาษาต่างๆ สิ่งแรกที่ควรอ่านคือเขาเขียน Code ยังไง
นอกจากเรื่องความสวยงามแล้ว Code Convention ที่ดีจะสามารถแก้ไขโดยผู้อื่น
Style Guide ที่เขียนใส่ Blog นี้ถูกรวบรวมมาจากที่นี่นะ (แล้วก็มีที่เขียนเพิ่มโดยผมเองนิดหน่อย)
บางอย่างก็ไม่ได้หยิบมา อยากอ่านฉบับเต็มก็เชิญได้ที่ต้นฉบับเลยครับ :)
https://github.com/NYTimes/objective-c-style-guide
https://github.com/github/objective-c-conventions
Dot-Notation Syntax
Dot-Notation Syntax จะใช้ทุกครั้งที่เข้าถึงหรือเปลี่ยนแปลง Property ต่างๆ ของวัตถุใดๆ ส่วน Bracket Notation จะใช้ในกรณีอื่นๆ โดยมากแล้วจะเป็นการเรียก method ต่างๆ (Class method, Instance method)
ตัวอย่าง
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
ไม่ควร
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
Pragma mark
ใช้ #pragma mark ในการแบ่งกลุ่มของ method ที่ทำหน้าต่างกัน เพื่อให้เกิดความเป็นระเบียบเวลาเราใช้ method jumper (ชื่อนี้หรือปล่าวไม่แน่ใจ) ใน Xcode ตัวอย่าง #pragma mark ที่ผมใช้อยู่บ่อยๆ นะ
ที่เห็นนี่คือหลักๆ ข้างล่างของ #pragma mark พวกนี้ก็จะเป็น Datasource และ Delegate ต่างๆ ซึ่งจะเขียนแยกกันอย่างชัดเจนเลย
Spacing
- การย่อหน้า (Indent) ควรใช้ 4 spaces ไม่ควรใช้ Tab หรือผสมกัน ซึ่งถ้าถนัดกด Tab มากกว่าสามารถตั้งค่าให้ Tab เป็น Indent (Soft Tab) ได้ใน Perfernece ใน Xcode
- วงเล็กปีกกาสำหรับ method หรือวงเล็ปปีกกาอื่นๆ (if / else / switch / while) จะเปิดในบรรทัดเดียวกันกับชื่อ method หรือเงื่อนไขที่ควรเป็นจริง และปิดวงเล็บด้วยการขึ้นบรรทัดใหม่
ตัวอย่าง
if (user.isHappy) {
//Do something
}
else {
//Do something else
}
- เวลาเขียน Math Expression ควรเว้นวรรคระหว่าง 1st Operand, Operator, 2nd Operand ด้วย ยกเว้นเครื่องหมาย Increment (++, --) เช่น b = a + c, c * b * a, (a++ + ++b)
- ในการเขียน method มากกว่า 1 method ให้เว้น 1 บรรทัดสำหรับแต่ละ method ด้วยเพื่อให้มองดูแยกกันชัดเจน
- @synthesize และ @dynamic ควรใช้ 1 บรรทัดต่อ 1 property
Conditionals
ในการเขียนเงื่อนไข if แม้จะเป็น if ที่มีเนื้อหาเพียงบรรทัดเดียว ก็ควรจะมี {} ครอบไว้เพื่อกำหนดขอบเขตให้ชัดเจน และใช้กฎของการครอบปีกกาด้านบน
ตัวอย่าง
if (!error) {
return success;
}
ไม่ควร
if (!error)
return success;
หรือ
if (!error) return success;
Ternary Operator
Ternary Operator (?:) จะใช้ต่อเมื่อต้องการให้โค้ดเคลียร์ และเป็นระเบียบเรียบร้อยขึ้น โดยมากแล้วจะใช้กับการตรวจสอบแบบ "เงื่อนไขเดียว" เท่านั้น
ตัวอย่าง
result = a > b ? x : y;
ไม่ควร
result = a > b ? x = c > d ? c : d : y;
Methods
ในการประกาศ method ควรจะใส่ spac ไปด้านหลังสัญลักษณ์บอกขอบเขต (สัญลักษณ์ -/+) และใช้ space ในการแยกส่วนต่างๆ ของชื่อ method (ถ้าเป็นไปได้ ลองหาคำเชื่อมประโยคที่เหมาะสมอย่างเช่น with, and มาใช้จะทำให้อ่านง่ายขึ้น)
ตัวอย่าง
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
Variables
ชื่อตัวแปรควรจะตั้งให้สื่อความหมายมากที่สุดเท่าที่จะเป็นไปได้ว่าตัวแปรนี้ชี้ไปหาอะไร ชื่อตัวแปรอักษรเดียวไม่ควรใช้เป็นอันขาดนอกเสียจากในคำสั่งทำซ้ำ for()
เครื่องหมาย Asterisks (*) เป็นตัวบ่งบอก Pointer ของตัวแปรนั้นๆ ควรจะเขียนให้เครื่องหมาย * ติดกับชื่อตัวแปร เช่น NSString *text ไม่ใช่ NSString* text หรือ NSString * text (อาจารย์ผมท่านเขียนแบบ NSString* text ท่านอธิบายไว้ว่า นี่เป็น Pointer ที่ชี้ไปหาตัวแปรชนิด NSString ก็ถือว่าเข้าใจได้นะ)
ควรใช้ Property ในการประกาศ Instance Variable เนื่องจากไม่ควรเข้าถึงตัวแปรตรงๆ (ผ่าน _variableName) ยกเว้นใน method การตั้งค่าเริ่มต้นต่างๆ (init, initWithCoder: และอื่นๆ), method ในการเคลียร์หน่วยความจำ (dealloc) และ method สำหรับการเข้าถึง หรือเปลี่ยนแปลงตัวแปรที่เขียนขึ้นเอง
ตัวอย่าง
@interface NYTSection: NSObject
@property (nonatomic) NSString *headline;
@end
ไม่ควร
@interface NYTSection : NSObject {
NSString *headline;
}
Naming
ตามคำแนะนำในการตั้งชื่อของ Apple ชื่อ method หรือตัวแปรที่ยาว และบรรยายตัวเองได้เป็นเรื่องที่ดีมาก ส่วนของรูปแบบโดยมากแล้วเวลาที่ผมตั้งชื่อจะใช้หลักการแบบนี้
- lower camel-case => ชื่อตัวแปร, ชื่อค่าคงที่ในกรณีที่ใช้ static const (เช่น bookArray)
- upper camel-case => ชื่อ Class (เช่น ScifiBook)
- upper under score => ค่าคงที่ในกรณีใช้ #define (BOOK_AMOUNT)
ตัวอย่าง
UIButton *settingsButton;
ไม่ควร
UIButton *setBut;
ตัวอักษร 3 ตัว (หรือ 2 ตัว) นำหน้าเรียกว่า Class Prefix จะใช้กับชื่อค่าคงที่ หรือชื่อ Class เท่านั้น ค่าคงที่นั้นควรจะตั้งชื่อให้สัมพันธ์กับชื่อ Class และมีลักษณะของ camel-case
ตัวอย่าง
static const NSTimeInterval NYTArticleViewControllerNavigationFadeAnimationDuration = 0.3;
ไม่ควร
static const NSTimeInterval fadetime = 1.7;
Property ต่างๆ ควรตั้งชื่อด้วยลักษณะ camel-case ที่นำหน้าด้วยอักษรตัวเล็ก (thisIsABook) โดย Xcode 4.5 ขึ้นจะทำ synthesize ให้กับตัวแปรที่ประกาศขึ้นให้อัตโนมัติ ในรูปแบบ
@synthesize descriptiveVariableName = _descriptiveVariableName;
Underscores
ในการเข้าถึงหรือเปลี่ยนแปลงค่าของ property ต่างๆ ควรใช้ self. นั่นหมายถึงจะเข้าถึง property ตัวนั้นผ่าน getter, setter ที่มี (จากการทำ synthesize หรือเขียนขึ้นมาเอง) ไม่ใช่เข้าถึงตัวแปรตรงๆ การเข้าถึงตัวแปรตรงๆ (เมื่อ synthesize ด้วยรูปแบบด้านบนจะเข้าถึงด้วย _variableName) จะใช้ในกรณีตั้งค่าเริ่มต้น หรือมีการเข้าถึงใน Initialize method, dealloc method เท่านั้น
Literals
NSString, NSDictionary, NSArray และ NSNumber มีรูปแบบในการสร้างแบบใหม่เมื่อเราต้องการสร้างวัตถุที่ไม่สามารถแก้ไขได้ และควรระวัง อย่ากำหนดค่า NSArray หรือ NSDictionary ด้วยค่า nil (มีผลทำให้ App Crash)
ตัวอย่าง
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @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 *ZIPCode = [NSNumber numberWithInteger:10018];
CGRect Functions
เมื่อต้องการเข้าถึง x, y, width หรือ height ของ CGRect ควรจะใช้ CGGeometry Functions แทนที่จะเข้าถึงค่านั้นๆ ด้วย struct member
ตัวอย่าง
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
ไม่ควร
CGRect frame = self.view.frame;
CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
Constants
ควรจะใช้ static const แทน macro (#define)
ตัวอย่าง
static NSString * const NYTAboutViewControllerCompanyName = @"The New York Times Company";
static const CGFloat NYTImageThumbnailHeight = 50.0;
#define CompanyName @"The New York Times Company"
#define thumbnailHeight 2
Enumerated Types
เมื่อมีการใช้ enum แนะนำให้ใช้การประกาศ NS_Enum เนื่องจากมีคุณสมบัติ strong type checking และ code completion
ตัวอย่าง
typedef NS_ENUM(NSInteger, NYTAdRequestState) {
NYTAdRequestStateInactive,
NYTAdRequestStateLoading
};
Private Properties
property ที่ต้องการให้เป็นส่วนตัวใช้เฉพาะใน class นั้นๆ ให้ประกาศ private interface ขึ้นใน implementation file และไม่จำเป็นต้องตั้งชื่อให้สื่อความหมายว่านี่เป็น private property แต่อย่างใด
ตัวอย่าง in .m file
@interface NYTAdvertisement ()
@property (nonatomic, strong) GADBannerView *googleAdView;
@property (nonatomic, strong) ADBannerView *iAdView;
@property (nonatomic, strong) UIWebView *adXWebView;
@end
Booleans
เนื่องจาก nil หมายถึง NO ได้เช่นกัน ดังนั้นจึงไม่จำเป็นต้องเปรียบเทียบ NO ในเงื่อนไข ในกรณี YES ก็เช่นกัน
ตัวอย่าง
if (!someObject) {
}
if (someObject == nil) {
}
if (isAwesome)
if (![someObject boolValue])
ไม่ควร
if ([someObject boolValue] == NO)
if (isAwesome == YES) // Never do this.
@property (assign, getter=isEditable) BOOL editable;
Singletons
ในการเขียน Shared Singleton Object ให้ใช้ Thread-safe pattern
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
และอย่างหนึ่งสำหรับการปิดท้ายคือ นี่คือบทความที่บอกถึงสิ่งที่ "ควร" จะเป็นในการเขียนโค้ด ไม่ใช่ข้อบังคับ ดังนั้น จะเขียนยังไงก็ได้ โค้ดบางรูปแบบ เราอาจจะเขียนอีกแบบ และก็มีเหตุผลรองรับในการเขียนของตัวเอง ฉันเข้าใจแบบนี้ ไม่ใช่เรื่องผิดครับ ลองตั้ง Protocol สำหรับตัวเองหรือ Team ดูนะครับ เวลาที่เราต้อง Maintain Code คนอื่น หรือคนอื่นต้องมาเขียนต่อเรา จะได้ไม่ลำบาก :)
สามารถแนะนำมาได้นะครับ ใครใช้ Style ไหนที่น่าสนใจอีก ... ผมจะได้เอามาเพิ่มเติมให้ใน Blog
เดี๋ยวผมเจออะไรที่สวยกว่า หรือน่าสนใจจะเอามาอัพเดทให้ที่นี่เหมือนกัน ^^


Comments
Post a Comment