Cocoa Style for Objective-C: Part I

สำหรับการเริ่มเขียนภาษา Objective-C หรือภาษาคอมพิวเตอร์อื่นๆ ไม่ว่าจะเป็น Java, C หรือ PHP สิ่งสำคัญอย่างหนึ่งนอกจากจะต้องเข้างใจกลไกการทำงานของภาษานั้นๆ แล้ว ก็จำเป็นต้องเข้าใจธรรมชาติของลักษณะการกำหนดชื่อคลาส เมธอด และตัวแปร นั่นนอกจากจะทำให้โค้ดที่เราเขียนขึ้นเองนั้นเป็นที่เข้าใจง่ายแล้ว ก็ยังทำให้เราเข้าใจโค้ดอื่นๆ ที่เราต้องการได้ เช่น เฟรมเวิร์คภายนอก สำหรับภาษา Objective-C เอง ทาง Apple ก็ได้วางแนวทางการสำหรับวิธีการเขียนโค้ดให้กับนักพัฒนาเพื่อให้โค้ดเข้าใจได้ง่ายมากที่สุด ซึ่งนอกจากที่เขียนไว้เป็นลายลักษณะอักษรแล้วก็ยังมีบางส่วนที่ไม่ได้กำหนดไว้ (แต่กลายเป็น de facto) บล็อกต่อไปในี้ก็เลยตั้งใจว่าจะนำเอาเรื่องพวกนี้มาอธิบายไว้ก่อน ก่อนที่เริ่มกับ Objective-C มากกว่านี้ โดยบล็อกจะแบ่งออกเป็น 2 ตอนด้วยกันซึ่งเนื้อหานี้จะเรียบเรียงมาจากบทความของ CocoaDevCentral 2 บทความ ได้แก่ Cocoa Style for Objective-C: Part I และ Cocoa Style for Objective-C: Part II โดยที่จะแบ่งเป็น 2 ส่วนคือ

      Part I: การตั้งชื่อพื้นฐานของ Class, Variable, Accessor หรือ Method
      Part II: รายละเอียดอื่นๆ สำหรับชื่อ Method, Global Symbols, Parameter และอื่นๆ

การใส่ความหมายลงไปในชื่อ

ยิ่งนานวันเข้าการพัฒนาแอพพลิเคชั่นก็เริ่มใช้โค้ดน้อยลงเรื่อยๆ การตั้งชื่อคลาส ฟังก์ชั่น เมธอด หรือตัวแปรให้สื่อความหมาย หรือเข้าใจแนวคิดการตั้งชื่อของภาษานั้นๆ ที่เราสนใจก็เป็นเหมือนทางลัดอีกทางหนึ่งที่ช่วยให้เข้าใจกลไกของภาษานั้นได้เร็วยิ่งขึ้น ซึ่งใน Cocoa ถือว่าการตั้งชื่อสั้นๆ เช่นการใช้ตัวย่อนั้นจะทำให้การสื่อความหมายออกมาคลุมเครือ และสุดท้ายก็จะนำไปสู่ข้อผิดพลาดของแอพพลิเคชั่นได้

สำหรับ Cocoa นั้น ให้ความสำคัญกับการตั้งชื่ออย่างมาก โดยชื่อที่ตั้งขึ้นนี้ต้องเป็นชื่อที่อธิบายถึงการทำงานของคลาส เมธอด หรือตัวแปรนั้นๆ เข้าใจได้ง่ายไม่กำกวม ยกตัวอย่าง เช่น UITableViewController, UITableViewCell, NSFetchedResultsController, NSManagedObjectContext จะเห็นได้ว่าชื่อคลาสต่างๆ ที่ยกขึ้นมานี้เป็นชื่อที่แม้ว่าเราจะไม่รู้ว่าจะทำงานกับมันอย่างไร แต่เราก็ยังเข้าใจได้ว่ามันจะทำงานหรือแสดงผลออกมาแบบไหน

ซึ่งวิธีการกำหนดชื่อในรูปแบบนี้เป็นสิ่งสำคัญมากสำหรับ Cocoa programmer

Class Name

เช่นเดียวกันกับวิธีการตั้งชื่อคลาสของภาษาอื่นๆ ที่ชื่อคลาสต้องขึ้นต้นด้วยตัวอักษรใหญ่ (ตัวพิมพ์ใหญ่) เท่านั้น หลังจากนั้นให้ใช้ตัวาอักษรใหญ่เฉพาะกับตัวแรกของคำเท่านั้น แต่สำหรับภาษา Objective-C มีอย่างหนึ่งที่แปลกออกไป นั่นเพราะว่า Objective-C ไม่มี namespace (นึกถึง Package ของ Java) ทำให้ชื่อคลาสมีโอกาสซ้ำกันได้ ซึ่งปัญหานี้ Cocoa แก้ด้วยการใช้คำนำหน้า (Prefix) กับทุกไฟล์ที่พัฒนาขึ้น ซึ่งโดยทั่วไปแล้วจะใช้ชื่อย่อของผู้พัฒนานำหน้าชื่อคลาส ยกตัวอย่างเช่นชื่อย่อของผมคือ SL คลาสที่ผมพัฒนาขึ้นมาก็มักที่จะใช้ SL นำหน้า ได้แก่ SLTableViewCell, SLNetworkConnection หรือ SLMessage

ซึ่งทาง Apple ก็ทำเช่นเดียวกัน โดยใช้ NS ซึ่ง NS นี้เป็นตัวย่อของ NeXTSTEP หรือกล่าวได้ว่า บรรดาคลาสที่ใช้ NS เป็น Prefix นี้ ก็คือมรกดตกทอดมาจากครั้งที่ Apple ซื้อ NeXTSTEP มาเป็นรากฐานของ OS X นั่นเอง

การตั้งชื่อตัวแปร

ชื่อตัวแปรต้องขึ้นต้นด้วยตัวอักษรเล็ก (ตัวพิมพ์เล็ก) หลังจากนั้นก็ใช้การกำหนดชื่อตามวิธีการ CamelCase (เช่นเดียวกันกับการตั้งชื่อคลาส) ซึ่งอย่างที่ได้เกริ่นนำไปในตอนต้นว่า Objective-C ให้ความสำคัญกับความหมายของชื่อที่ตั้งมาก ดังนั้นชื่อที่ตั้งต้องเป็นชื่อที่อธิบายความหมายได้จัดเจน ไม่คลุมเครือ ยกตัวอย่างเช่น

// Correct
NSString *hostName;
NSNumber *ipAddress;
NSArray  *accounts;

// Incorrect
NSString       *HST_NM;  // all capital and too terse
NSNumber       *theip;   // abbreviation ?
NSMutableArray *nsma;    // what is the 'nsma' ?

นอกจากนั้นแล้ว ชื่อตัวแปรต้องไม่ขึ้นต้นด้วยตัวเลข และต้องไม่มีเครื่องหมายพิเศษผสมอยู่ในชื่อตัวแปร ยกเว้นเครื่องหมาย ‘_’ ที่สามารถใช้ร่วมในตัวแปรได้ แต่หากเครื่องหมาย ‘_’ นำหน้าตัวแปรใด ตัวแปรนั้นควรเป็นตัวแปรแบบ private เท่านั้น

การตั้งชื่อตัวแปรเพื่อบ่งชี้ประเภทตัวแปร

เมื่อต้องเขียนโปรแกรมจริงแล้วเราจะไม่ใส่คำเข้าไปเพื่อบ่งชี้ประเภทตัวแปรในกรณีที่เป็นตัวแปรชนิดพื้นฐาน เช่น NSString, NSArray, NSNumber หรือ BOOL เพราะใน Objective-C จะใช้การสื่อผ่านความหมายของคำแทน เช่น ในกรณีที่ตัวแปรมีชนิดเป็น NSArray ก็จะทำให้ชื่อตัวแปรนั้นอยู่ในรูปพหุพจน์ ตัวอย่างเช่น

// Correct
NSString        *accountName;
NSMutableArray  *mailboxes;
NSArray         *defaultHeaders;
BOOL             userInputWasUpdated;

// Fine, but not quite good. It might redundance with variable's meaning.
NSString        *accountNameString;
NSMutableArray  *mailboxArray;
NSArray         *defaultHeaderArray;
BOOL             userInputWasUpdatedBOOL;

แต่ในกรณีที่ตัวแปรไม่ได้มีชนิดข้อมูลทั่วไปเช่นข้างต้น ชื่อตัวแปรก็ควรที่จะแสดงให้เห็นว่าตัวแปรดังกล่าวเป็นชนิดใด

// reflect name
NSImage             *previewPaneImage;
NSProgressIndicator *dataUploadIndicator;
NSFontManager       *fontManager;

// plural type such NSDictionary, NSSet
NSDictionary        *keyedAccountNames;
NSDictionary        *messageDictionary;
NSIndexSet          *selectedMailboxesIndexSet;

ชื่อเมธอด

ปัญหาอย่างหนึ่งที่เกิดขึ้นกับภาษาอื่น ก็คือเมื่อมีการเรียกใช้ฟังก์ชั่นหรือเมธอดจะมีเพียงชื่อเมธอดที่พอให้เราเดาได้เท่านั้น แต่ไม่สามารถเดาหน้าที่ของพารามิเตอร์แต่ละตัวได้เลย แต่สำหรับการตั้งชื่อเมธอดใน Objective-C นั้น “จะตั้งชื่อเมธอดโดยอาศัยพฤติกรรมการใช้ทำงานของเมธอดนั้นเป็นหลัก” ดังนี้

// assume that fileWrapper exists, and handle for write to disk

// Other language
fileWrapper.write(path, true, true);
// Objective-C
[fileWrapper writeToFile:path atomically:YES updateFilenames:YES];

// PS. 'YES' in Cocoa is mean true

เพราะสิ่งสำคัญก่อนการตั้งชื่อเมธอดนั้น ให้ถามตัวเองก่อนว่า ชื่อนี้อธิบายพฤติกรรมการทำงานของเมธอดนี้ชัดพอแล้วหรือยัง แล้วยิ่งถ้ามันปะปนอยู่กลางโค้ดหลายร้อยหลายพันบรรทัดเราจะไม่สับสน เพราะโปรแกรมเมอร์ต้องอ่านโค้ดให้มากกว่าเขียนโค้ด ดังนั้น Objective-C และ Cocoa จึงออกแบบมาเพื่อให้อ่านได้และเข้าใจได้ง่ายขึ้นนั้นเอง ลองเปรียบเทียบประโยคด้านล่างกับชื่อเมธอดดูครับ

// "open the file with this application and deactivate"

[finder openFile:mailing withApplication:@"MailDrop" andDeactivate:YES];

Method names: Accessors

อีกเรื่องหนึ่งที่ต่างออกไปจากภาษาอื่นคือ Accessor ใน Objective-C นั้นจะไม่ใช้ get นำหน้าชื่อ getter method

// Correct
- (NSString *) name;
- (int) age;

NSString *name = [object name];
int       age  = [object age];

// Incorrect
- (NSString *) getName;
- (int) getAge;

NSString *name = [object getName];
int       age  = [object getAge];

แต่ get จะใช้กับการนำค่าที่อยู่ใน Memory Address มาแสดง

// copy objects from the NSArray to the buffer

id *buffer = (id *) malloc( sizeof( id ) * [array count] );
[array getObjects:buffer];

ส่วน set ก็ใช้เหมือนกับภาษาอื่น