รูปแบบที่ควรเป็น ในการเขียนโคดภาษา Java

สารบัญ

  1. ทำความเข้าใจกันก่อน
  2. โครงสร้างในการเขียน
  3. การประกาศต่างๆ
  4. การเขียนคำชี้แจง (Comment)
  5. การเขียนโค้ด

ทำความเข้าใจกันก่อน

แม้ compiler ของภาษา Java จะเปิดกว้างให้เราสามารถที่จะเขียนโค้ดในรูปแบบใดก็ได้ที่เราถนัด ซึ่งทำให้เราเริ่มต้นเขียนได้ง่าย แต่สิ่งนี้ก็ทำให้โค้ดของเรายากแก่การทำความเข้าใจในภายหลัง ชาว Java ขอแนะนำให้เราเขียนโค้ดไปในแนวทางเดียวกัน โดยยึดรูปแบบที่ง่ายที่สุดต่อการทำความเข้าใจ

เอกสารนี้เป็นแนวทางการเขียนโค้ดที่ผ่านการทดสอบมานานโดยกลุ่มนักเขียนโปรแกรม เพื่อให้ สอดคล้อง และง่าย ต่อการอ่าน หรือปรับแต่งในภายหลัง แน่นอนว่าแนวทางนี้ไม่เฉพาะ ชาว java เท่านั้นที่จะใช้ แต่เราสามารถนำมันไปใช้ในภาษาอื่นๆ ได้อีกด้วย

95% ของมาตรฐานในเอกสารนี้ จะพบได้ใน code ของ JDK ส่วนที่เหลืออีกเล็กน้อย ได้มาจากการตกลงกันอย่างหนักภายในกลุ่ม programmer ของเรา

ในเอกสารนี้จะครอบคลุมเฉพาะปัญหาที่อาจจะทำให้เกิดความสับสน หรือยากจะทำความเข้าใจ ส่วนรูปแบบที่ไม่เป็นปัญหาก็ขอละไว้ละกัน แต่อย่างไรก็ตามเราจะเห็นรูปแบบพวกนั้นจะแทรกอยู่กับโค้ดตัวอย่างในเอกสารฉบับนี้ครับ

โครงสร้างในการเขียน

การเยื้อง (Indentation)

การเยื้องทั้งหมด ให้ใช้ช่องว่างจำนวน 4 ตัวอักษร แทนการใช้ tab

void foo()
{
  while ( bar > 0 )
  {
    System.out.println();
    bar-- ;
  }

  if ( oatmeal == tasty )
  {
    System.out.println(Oatmeal is good and good for you);
  }
  else if ( oatmeal == yak )
  {
    System.out.println(Oatmeal tastes like sawdust);
  }
  else
  {
    System.out.println(tell me pleeze what iz dis 'oatmeal');
  }

  switch( suckFactor )
  {
    case 1:
      System.out.println(This sucks);
      break;
    case 2:
      System.out.println(This really sucks);
      break;
    case 3:
      System.out.println(This seriously sucks);
      break;
    default:
      System.out.println(whatever);
      break;
  }
}

เหตุผล : โปรแกรมทุกโปรแกรมสามารถทำงานได้ดีกับช่องว่างที่เป็นตัวอักษร โปรแกรมส่วนใหญ่จะมีทั้งตัวอักษรและ tabs นั้นทำให้บางบรรทัดเราใช้ย่อหน้าเป็นตัวอักษร และบางบรรทัดเป็น tab ซึ่งทำให้เกิดปัญหาว่า ถ้าเรากำหนดให้ tab เท่ากับ 4 ตัวอักษร แล้วส่งโปรแกรมไปให้เพื่อนซึ่ง กำหนดให้ tab เท่ากับ 8 ตัวอักษร โค้ดของเราจะดูปัญญาอ่อนมาก

ทุก if, while และ for จะต้องเป็นย่อหน้า ไม่เว้นแม้แต่ชุดที่มีคำสั่งเดียว

if ( superHero == theTick ) System.out.println(Spoon!); // NO!

if ( superHero == theTick ) // YES!
{
    System.out.println(Spoon!);
}

เหตุผล : ถ้าเขียนโค้ดไปในทางเดียวกัน ก็จะทำให้ง่ายต่อการอ่านแถมอย่างน้อยการแก้ไขในภายหลังก็จะทำได้ง่ายทั้งในการลบและ การเพิ่ม

ช่องว่าง

ทุกๆ การกำหนดค่าต้องล้อมรอบด้วย ช่องว่าง

int theTick=5; // NO! Double click would get TheTick=5;
int theTick = 5 ; // YES! Double click gets TheTick
if (theTick==5) // NO! Double click gets (TheTick==5)
if ( theTick == 5 ) // YES! Double click gets TheTick

เหตุผล : เครื่องมือของโปรแกรมบางตัว จะจัดระเบียบโค้ด และระบุตัว(identifier) เมื่อคุณทำการ double click บนคำนั้นๆ และบาง double-click-function จะใช้ช่องว่างในการตรวจสอบ ดังนั้นทุกๆ คำจึงต้องล้อมรอบด้วยช่องว่าง

แต่บางครั้งการมีช่องว่างมากๆ ก็ทำให้เกิดปัญหาในการทำความเข้าใจกับการตรวจสอบ ค่าจริงเท็จ (boolean)

if ( ( hero == theTick ) && ( ( sidekick == arthur ) || ( sidekick == speak ) ) )
ควรจะเปลี่ยนให้เป็น boolean isTickSidekick = ( ( sidekick == arthur ) || ( sidekick == speak ) );
if ( ( hero == theTick ) && isTickSidekick )

นี้เป็นหัวข้อที่ถูกยกเว้นไม่ต้องทำตามกฎช่องว่าง:
ข้อยกเว้น เหตุผล ตัวอย่างที่ดี ตัวอย่างที่ไม่ดี
method names เป็นที่นิยมกันว่าตามหลังชื่อ method จะต้องเป็นวงเล็บเปิดทันที foo( i );
start();
foo (i);
start ();
array dereferences เป็นที่นิยมกันว่าตามหลัง array จะต้องเป็นวงเล็บก้ามปูเปิดทันที args[0];
tens[ i ];
args [0];
tens ;
pre- and post-increment and decrement operators เป็นที่นิยมกันว่าคำสั่ง unary operator จะต้องตามหลังด้วย operation ทันที. ++count ;
i-- ;
++ count;
i --
cast operator เป็นที่นิยมกันว่า cast จะต้องเขียนโดยไม่มีช่องว่าง (MyClass)v.get( 3 ); (MyClass) v.get( 3 );
( MyClass )v.get( 3 );

บรรทัดว่าง (Blank Line)

ใช้บรรทัดว่างเพื่อแยกโค้ดภายใน method ให้เป็นสัดส่วน และใช้บรรทัดว่างหนึ่งหรือสองบรรทัดระหว่างแต่ละ methods และ ลำดับของโค้ดภายใน class

class Order
{
   // field (attributes)
  
   // constructors

   // methods
}

ให้ private methods ขึ้นก่อน method ที่เรียกใช้มัน แต่ต้องอยู่หลัง constructors เว้นแต่ว่า constructors จะใช้มัน

โครงสร้างการเขียน File

package (ถ้าใช้) จะต้องมาเป็นอันดับแรก
import เป็นอันดับที่สอง
ตามมาด้วยคำบรรยาย (comment) ที่ไม่ได้ใช้สำหรับสร้าง javadoc
ทุกๆ เอกสารของที่จะเป็น javadoc ให้อยู่ต่อจากนี้
ให้ class เป็นอันดับสุดท้าย

เงื่อนไขนี้ใช้เฉพาะเมื่อหนึ่ง file มีแค่หนึ่ง class (ยกเว้น inner classes)

Example:

package misc;
import java.io.*;
import java.net.*;

/** this class does cool stuff
 @author Joe Programmer
*/

class SpaceMonkey
{
 ...
}

จำนวนตัวอักษรในแต่ละบรรทัด

พยายามไม่เขียนให้แต่ละบรรทัดมีจำนวนอักษรมากกว่า 120 ตัวอักษร แต่โดยมากแล้วเราควรจะเขียนโค้ดให้น้อยกว่า 80 ตัวอักษรในหนึ่งบรรทัด และถ้าโค้ดของเรา เริ่มต้นย่อหน้าก็อยู่ขวามากแล้ว ก็ควรจะยกโค้ดส่วนนั้นขึ้นไปเป็นอีก methods

เหตุผล : การแก้ไข และการพิมพ์(printing) ที่มีอยู่ทั่วไป จะใช้งานโค้ดง่ายกว่าถ้าแต่ละบรรทัดมีไม่เกิน 120 ตัวอักษร อีกทั้งบรรทัดยาวๆ ยังสร้างปัญหาในการทำงานกับมัน

วงเล็บ

นอกจากเราจะใช้วงเล็บเพื่อกำหนดลำดับการทำงานของสมการแล้ว เรายังควรใช้วงเล็บทำให้สมการดูง่าย เมื่อมันมีขนาดใหญ่ หรือซับซ้อน

การประกาศต่างๆ (Identifiers)

ทุกๆ การประกาศ ควรใช้ตัวอักษร (A ถึง Z และ a ถึง z) และตัวเลข (0 ถึง 9) เท่านั้น พยายามอย่าใช้ขีดเส้นใต้(_, underscores) หรือ เครื่องหมายบาท (฿) หรือ ตัวอักษรที่ไมใช่ ascii (non-ascii characters)

สัญลักษณ์ต่างๆ(เช่น Hungarian Notation และ Scope Identification) ก็ไม่ควรใช้

public class Person
{
    private String sFirstName; // NO! (Hungarian Notation, s หมายถึงตัวแปรนี้เก็บค่า String)
    private String firstName; // YES!

    private String mLastName; // NO! (Scope Identification, m หมายถึงตัวแปรที่เป็นสมาชิกของ member variable)
    private String lastName; // YES!
}

Classes และ interfaces

ในการประกาศ class และ interface เราจะใช้ภาษาอังกฤษทั้งตัวใหญ่และตัวเล็ก โดยให้ตัวอักษรตัวแรกของแต่ละคำให้เป็นตัวใหญ่ (Uppercase) รวมทั้วตัวอักษรตัวแรกของชื่อด้วย นอกนั้นให้ใช้อักษรตัวเล็กทั้งหมด ยกเว้นว่าจะเป็นตัวย่อ ซึ่งต้องเป็นตั้วใหญ่ ก็ให้ใช้อักษรตัวใหญ่ทั้งหมดครับ

Examples:

Customer
SalesOrder
TargetURL
URLTarget

Packages

ชื่อของ Package ให้เป็นอักษรตัวเล็กเท่านั้น (lower case) และพยายามใช้ตัวอักษรให้น้อยกว่าแปด (8) ตัวอักษร ให้พยายามหลีกเลี่ยงการใช้คำหลายคำในชื่อ package

Examples:

common
core
lang

สำหรับการประกาศอื่นๆ

ทุกๆ การประกาศไม่ว่าจะเป็น including(ให้ใช้ตามความจำเป็น) attributes, variables, methods และ parameters ให้พยายามตั้งชื่อที่เป็นสากล รวมทั้งการประกาศ final (ให้ใช้อักษรตัวใหญ่ทั่งหมด ตามที่นิยมกันในภาษา C แม้ว่าจะขัดกับหลักของ OO ก็ตาม) สำหรับตัวอักษรตัวแรกของแต่ละคำให้ใช้อักษรตัวใหญ่ ยกเว้นอักษรตัวแรกของชื่อ อักษรที่เหลือทั้งหมดให้ใช้ตัวเล็ก แต่ถ้าคำใดเป็นอักษรย่อ ก็ให้ใช้ตัวอักษรตัวใหญ่ทั้งหมด ยกเว้นอักษรย่อนั้นเป็นคำแรกของชื่อ

Examples:

customer
salesOrder
targetURL
urlTarget

ชื่อพิเศษ Getters และ Setters

Methods ที่ทำหน้าที่กำหนดสถานะ มักจะใช้คำนำหน้าว่า set ในชื่อของ method

Methods ที่ส่งค่าตัวแปลกลับ (return) เป็น boolean object มักใช้คำนำหน้าว่า is ในชื่อของ method นอกนั้น methods ที่มีการส่งค่าสถานนะของ object กลับให้ใช้คำนำหน้าว่า get ในชื่อ method

Examples:

setEnabled()
getName()
isEnabled()

การเขียนคำชี้แจง (Comment)

ให้ใช้คำชี้แจงรูปแบบ // สำหรับจุดหลักๆ ของคำชี้แจง (comment).

สำหรับคำชี้แจงรูปแบบ /** */ ให้ใช้เพื่อสร้าง javadoc

พยายามใช้คำชี้แจงรูปแบบ /* */ สำหรับคำชี้แจงทั่วไปที่ไม่ค่อยเกียวกับโค้ด

การเขียนคำชี้แจงเพื่อสร้าง Javadoc

Javadoc ถูกใช้เพื่อสร้างเอกสารที่บอกถึงความเชื่อมโยง class, attributes and methods ที่ถูกใช้โดย classes อื่นๆ คำชี้แจงจะต้องเขียนติดอยู่กับสิ่งๆที่เราต้องการ เพื่อให้อยู่ในเอกสาร (javadoc)

คำชี้แจงของ Javadoc ไม่จำเป็นสำหรับ methods ที่ไม่ค่อยสำคัญ อย่างเช่น

public static void main( String[] args )

หรือ

public int getX()

คำชี้แจง Javadoc ไม่จำเป็นสำหรับ parameters ที่ไม่ค่อยสำคัญ อย่างเช่น

public void setX( int newX )

และคำชี้แจง Javadoc ก็ไม่จำเป็นสำหรับ class ที่ไม่ต้องการให้ Class อื่นๆ เข้ามาใช้ อย่างเช่น servlets และ EJB's

ก่อนที่คุณจะส่งโค้ดของคุณไปให้กลุ่ม คุณจะต้องใส่ JavaDoc และต้องคิดถึงการให้ตัวอย่างการใช้งานพร้อมผลลัพท์ เพื่อให้แน่ใจว่า เอกสารของคุณสร้างมาให้คนอ่าน และไม่วุ่นวาย

ถ้าคำชี้แจง JavaDoc สามารถเขียนได้ภายในหนึ่งบรรทันให้คุณเขียนในลักษณะนี้:

/** Used to mark spots */
int x ;

แต่ถ้าคำชี้แจง JavaDoc ไม่สามารถเขียนได้ภายในหนึ่งบรรทัด ก็ให้เขียนในลักษณะนี้:

/** Set how much to grow when growth is needed.

Smaller values will usually save memory, but frequent
reallocation may take a lot of time.

@param howMuch The number of extra ints to allocate when
memory reallocation is required. Values must be greater than
zero.
*/

public void setExtra( int howMuch )
{
}

เราสามารถใช้ HTML tags <p> และ <pre> ได้ โดย <p> หมายถึงการสิ้นสุดย่อหน้า ส่วน <pre> .. </pre> เพื่อกำกับส่วนที่ต้องการแสดงอักษรแบบ fixed font และให้แสดงช่องว่างด้วย (ปกติ html จะแสดงช่องว่างหลายช่องด้วยช่องเดียว)

JavaDoc ยังให้คุณใช้ HTML ต่างๆ ที่คุณต้องการ แต่ขอให้คุณไม่ใช้ header tags (<h1>, <h2>, etc.) แต่จะเป็นการดีถ้าให้ใช้ <b>.. </b> เพื่อทำตัวหนา หรือ <i>.. </i> แทนตัวเอียงแทนที่จะใช้ header tags

JavaDoc จะตัดบรรทัดแรกของคำชี้แจงออก เพื่อนำไปใช้เป็นคำอธิบายสั้นๆ ใน table of contents เราคงต้องคำถึงถึงด้วยว่า เราคงต้องคิดด้วยว่าจะตัดที่ท่อนไหน JavaDoc กำหนดการตัดโดยใช้บรรทัดว่าง แต่ไม่นับเครื่องหมายคำถามที่ตามด้วยช่องว่าง หรือไม่ก็ ตามด้วย <p> tag (ให้ใช้บรรทัดว่างระหว่างประโยคและ <p> tag)

Classes
JavaDoc สำหรับเอกสารอธิบาย class ต้องประกอบด้วย:
* บทสรุป และสาระสำคัญ
* คำอธิบายในรายละเอียด
* ตัวอย่างโค้ดและตัวอย่างการใช้ class
* รายชื่อของผู้เขียนโดยใช้ JavaDoc @author tag
เพราะ feature ใน JavaDoc ทำให้เราต้องใส่เครื่องหมายดอกจันไว้หน้าบรรทัดเพื่อรักษาย่อหน้าของเราไว้.
Example:
/** A vector class optimized for working with ints.

 Like the Vector object, except rather than tracking a dynamic
 array of pointers to different objects, this is simply a
 dynamic array of ints. The advantage is speed and memory
 savings.

Example: <pre>
*
* // report longest lines
* TextFileIn f = new TextFileIn(blather.txt);
* IntVector v = new IntVector();
* int longestLine = 0 ;
* boolean done = false ;
* while ( ! done )
* {
* String s = f.readLine();
* if ( s == null )
* {
* done = true ;
* }
* else
* {
* int sLength = s.length() ;
* if ( sLength > longestLine )
* {
* longestLine = sLength ;
* }
* v.append( sLength );
* }
* }
* f.close();
* System.out.println(The longest lines are on line numbers:);
* for ( int i = 0 ; i < v.length() ; i++ )
* {
* if ( v.get( i ) == longestLine )
* {
* System.out.println( i );
* }
* }
 </pre>

 @author Adam Baum
 @author Justin Case
*/

public class IntVector
{
}

Methods

สำหรับ method ของเอกสาร JavaDoc จะต้องมี:

* บทสรุปสาระสำคัญของ method
* ใส่ทุกคำอธิบายในรายละเอียด (มากเท่าที่จะใส่ลงไปได้)
* รายการของ prameter ให้เขียนในรูปแบบ @param tag (ถ้ามี parameters)
* ค่าที่ส่งกลับมา (return) ให้เขียนในรูปแบบ @return tag (ถ้ามี returned)
* รายการของค่าผิดพลาดไม่ยอมรับ (exception) ให้เขียนในรูปแบบ @exception tag (ถ้ามี exceptions ออกมา)

Example:

/** Get a copy of one int. <p>

 Retrieve an int relative to the index provided.<p>

 @param Index Which int (0 is the first int).<p>
 @return The retrieved int or zero if Index is outside of 0..length.<p>

*/

public int get( int Index )
{
}

แนวทางการที่ดีของ document code

ถ้าคุณพยายามเขียนเอกสารเพื่ออธิบาย algorithm ที่ซับซ้อนของคุณ ให้เปลี่ยนเป็นพยายามเขียน algorithm ของคุณให้ง่าย โดยการดึงคำสั่งที่ซับซ้อนออกมาประกาศก่อนซะก่อน สิ่งนี้จะช่วยลดความซับซ้อนของ algorithm เพื่อให้ง่ายแก่การปรับแต่ง และการอ่านโดย ไม่จำเป็นต้องเขียนเอกสารอธิบายในส่วนนี้

ตัวอย่าง:

/** determine if the given year is a leap year. <p>

 The Gregorian calendar principal states that a leap year occurs
 every fourth year, except every 100 years, except every 400
 years. <p>

 If the year is evenly divisible by 400 or is evenly divisible by
 4 and not by 100, then it is a leap year. <p>

 @param year The year to be tested. Make sure this is a
    four digit year!<p>
 @return true if year is a leap year. <p>
*/

boolean isLeapYear( int year )
{
 return ( ( ( y % 400 ) == 0 ) || ( ( ( y % 4 ) == 0 ) &&
  ( ( y % 100 ) != 0 ) ) );
}

แก้ไขเป็น:

/** determine if the given year is a leap year. <p>

 The Gregorian calendar principal states that a leap year occurs
 every fourth year, except every 100 years, except every 400
 years. <p>

 @param year The year to be tested. Make sure this is a
    four digit year!<p>

 @return true if year is a leap year. <p>
*/

boolean isLeapYear( int year )
{
 boolean y4 = ( ( year % 4 ) == 0 ) ;
 boolean y100 = ( ( year % 100 ) == 0 ) ;
 boolean y400 = ( ( year % 400 ) == 0 ) ;
 return ( y400 || ( y4 && ! y100 ) );
}


หรือ:

/** determine if the given year is a leap year. <p>

 The Gregorian calendar principal states that a leap year occurs
 every fourth year, except every 100 years, except every 400
 years. <p>

 @param year The year to be tested. Make sure this is a
    four digit year! <p>
 @return true if year is a leap year. <p>

*/

boolean isLeapYear( int year )
{
 boolean returnVal = false ;
 if ( ( year % 400 ) == 0 )
 {
  // this is definitely a leap year
  returnVal = true ;
 }
 else if ( ( year % 4 ) == 0 )
 {
  // this is probably a leap year
  if ( ( year % 100 ) == 0 )
  {
   // this is definitely not a leap year
   // do nothing
  }
  else
  {
   // this is definitely a leap year
   returnVal = true ;
  }
 }
 return returnVal ;
}

การเขียนโค้ด

สิ่งที่ไม่ควรใช้อีกต่อไป

ไม่ควรใช้ลูป do..while

ถ้าต้องการให้ดีกว่า:

boolean done = false ;
do
{
 ...
} while( ! done )

ก็ให้แก้เป็น:

boolean done = false ;
while ( ! done )
{
...
}

เหตุผล: ลำรึกไว้ว่าเวลาที่โปรแกรมเมอร์อ่านโปรแกรมเค้าจะเริ่มอ่านตามลำดับการทำงาน ซึ่งไล่จากหัวลงมาท้าย ตอนที่ programmer พยายามจะทำความเข้าใจกับ loop เค้าจะเริ่มจากเงื่อนไขของมันก่อน ถ้าเราเอาเงื่อนไขไปไว้ที่ส่วนท้ายจะทำอ่านได้ลำบากมาก อีกทั้งจากประสบการณ์แล้วโปรแกรมส่วนใหญ่ไม่จำเป็นต้องใช้ do..while เราสามารถแก้ไขโค้ดไปในทางอื่นได้

อย่าใช้ return ในช่องกลางของ method เนื่องจาก return ถูกสร้างมาเพื่อใช้ในส่วนท้ายของ method เท่านั้น

เหตุผล: การใช้ return ในช่วงกลางของ method ทำให้ยากในการแยก method เป็นส่วนเล็กๆหรือเป็น method เล็กๆ และมันยังเป็นการบังคับให้ programmer ต้องกังวลกับทางออกของ method ที่มีมากกว่าหนึ่งที่อีกด้วย

อย่าใช้ continue อีกต่อไป

เหตุผล: การใช้งาน continue ทำให้ยากในการแยก method เป็นส่วนเล็กๆหรือเป็น method เล็กๆ และมันยังเป็นการบังคับให้ programmer ต้องกังวลกับทางออกของ method ที่มีมากกว่าหนึ่งที่อีกด้วย

อย่าใช้ break นอกจากใน Switch Statement เนื่องจาก break ควรถูกใช้สำหรับควบคุม switch statement เท่านั้น

เหตุผล: การใช้ break ยกเว้นในกรณีของ switch statement จะทำให้ยากในการแยก method เป็นส่วนเล็กๆหรือเป็น method เล็กๆ และมันยังเป็นการบังคับให้ programmer ต้องกังวลกับทางออกของ method ที่มีมากกว่าหนึ่งที่อีกด้วย

พยายามอย่าใช้ Compound Increment หรือ Decrement ให้ใช้คำสั่งหลายบรรทัดแทน increment หรือ decrement.

ตัวอย่าง :

foo( x++ ); // no!

foo( x ); // yes!
x++ ;

y += 100 * x++; // no!

y += 100 * x; // yes!
x++ ;

การกำหนดค่าเริ่มต้น

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

ตัวอย่าง :

int totalWide;
int firstWide = 20;
int secondWide = 12;
firstWide = doFoo( firstWide , secondWide );
doBar( firstWide , secondWide );
totalWide = firstWide + secondWide ; // ผิด

int firstWide = 20;
int secondWide = 12;
firstWide = doFoo( firstWide , secondWide );
doBar( firstWide , secondWide );
int totalWide = firstWide + secondWide ; // ถูก

int secondWide = 12;
int firstWide = doFoo( 20 , secondWide );
doBar( firstWide , secondWide );
int totalWide = firstWide + secondWide; // ถูกต้องที่สุด
ผู้เขียน goragod โพสต์เมื่อ 20 มี.ค. 2551 เปิดดู 35,217 ป้ายกำกับ Javascript
^