สไตล์โค้ดในเพจนี้เป็นกฎที่เข้มงวดสำหรับการใส่โค้ด Java ให้กับ Android Open Source Project (AOSP) การมีส่วนร่วมกับแพลตฟอร์ม Android ที่ไม่ปฏิบัติตามกฎเหล่านี้โดยทั่วไป จะไม่ได้รับการยอมรับ เราตระหนักดีว่าโค้ดที่มีอยู่ไม่ทั้งหมดปฏิบัติตามกฎเหล่านี้ แต่เราคาดว่าโค้ดใหม่ทั้งหมดจะสอดคล้อง ดู การ เข้ารหัสด้วยความเคารพ สำหรับตัวอย่างคำศัพท์ที่ใช้และหลีกเลี่ยงสำหรับระบบนิเวศที่ครอบคลุมมากขึ้น
คงเส้นคงวา
หนึ่งในกฎที่ง่ายที่สุดคือต้องสม่ำเสมอ หากคุณกำลังแก้ไขโค้ด ให้ใช้เวลาสักครู่เพื่อดูโค้ดรอบๆ และกำหนดสไตล์ของโค้ด หากรหัสนั้นใช้ช่องว่างรอบคำสั่ง if
คุณก็ควรทำเช่นกัน หากความคิดเห็นของโค้ดมีกล่องดาวเล็กๆ อยู่รอบๆ ตัว ให้แสดงความคิดเห็นของคุณให้มีกล่องดาวเล็กๆ อยู่รอบๆ ด้วย
จุดประสงค์ของการมีแนวทางสไตล์คือการมีคำศัพท์ทั่วไปเกี่ยวกับการเขียนโค้ด เพื่อให้ผู้อ่านสามารถจดจ่อกับสิ่งที่คุณกำลังพูด มากกว่าที่วิธีการพูดของคุณ เรานำเสนอกฎของสไตล์สากลที่นี่เพื่อให้คุณได้รู้คำศัพท์ แต่สไตล์ท้องถิ่นก็มีความสำคัญเช่นกัน หากโค้ดที่คุณเพิ่มลงในไฟล์ดูแตกต่างอย่างมากจากโค้ดที่มีอยู่รอบๆ โค้ด โค้ดดังกล่าวจะทำให้ผู้อ่านไม่เข้าจังหวะเมื่ออ่าน พยายามหลีกเลี่ยงสิ่งนี้
กฎของภาษาจาวา
Android ปฏิบัติตามข้อตกลงการเข้ารหัส Java มาตรฐานด้วยกฎเพิ่มเติมที่อธิบายไว้ด้านล่าง
อย่าละเลยข้อยกเว้น
การเขียนโค้ดที่ไม่สนใจข้อยกเว้นอาจเป็นเรื่องน่าดึงดูดใจ เช่น:
void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { } }
อย่าทำเช่นนี้ แม้ว่าคุณอาจคิดว่าโค้ดของคุณจะไม่มีวันเจอเงื่อนไขข้อผิดพลาดนี้หรือว่าการจัดการกับมันไม่สำคัญ แต่การละเลยข้อยกเว้นประเภทนี้จะสร้างทุ่นระเบิดในโค้ดของคุณเพื่อให้คนอื่นเรียกใช้งานในสักวันหนึ่ง คุณต้องจัดการกับทุกข้อยกเว้นในโค้ดของคุณอย่างมีหลักการ การจัดการเฉพาะแตกต่างกันไปขึ้นอยู่กับกรณี
" เมื่อใดก็ตามที่ใครบางคนมีประโยคที่ว่างเปล่า พวกเขาควรจะมีความรู้สึกที่น่าขนลุก มีบางครั้งที่มันเป็นสิ่งที่ต้องทำจริงๆ แต่อย่างน้อยคุณต้องคิดเกี่ยวกับมัน ใน Java คุณไม่สามารถหนีความรู้สึกน่าขนลุกได้ " — เจมส์ กอสลิ่ง
ทางเลือกที่ยอมรับได้ (ตามลำดับความชอบ) คือ:
- โยนข้อยกเว้นให้กับผู้โทรของวิธีการของคุณ
void setServerPort(String value) throws NumberFormatException { serverPort = Integer.parseInt(value); }
- โยนข้อยกเว้นใหม่ที่เหมาะสมกับระดับนามธรรมของคุณ
void setServerPort(String value) throws ConfigurationException { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { throw new ConfigurationException("Port " + value + " is not valid."); } }
- จัดการกับข้อผิดพลาดอย่างสง่างามและแทนที่ค่าที่เหมาะสมในบล็อก
catch {}
/** Set port. If value is not a valid number, 80 is substituted. */ void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { serverPort = 80; // default port for server } }
- จับข้อยกเว้นและโยนอินสแตนซ์ใหม่ของ
RuntimeException
สิ่งนี้เป็นอันตราย ดังนั้นให้ทำเฉพาะเมื่อคุณมั่นใจว่าหากเกิดข้อผิดพลาดนี้ สิ่งที่ต้องทำคือความผิดพลาด/** Set port. If value is not a valid number, die. */ void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { throw new RuntimeException("port " + value " is invalid, ", e); } }
- วิธีสุดท้าย หากคุณมั่นใจว่าการเพิกเฉยต่อข้อยกเว้นนั้นเหมาะสม คุณก็อาจเพิกเฉยได้ แต่คุณต้องแสดงความคิดเห็นด้วยว่าเหตุใดด้วยเหตุผลที่ดี
/** If value is not a valid number, original port number is used. */ void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { // Method is documented to just ignore invalid user input. // serverPort will just be unchanged. } }
อย่าจับข้อยกเว้นทั่วไป
อาจเป็นเรื่องน่าดึงดูดให้เกียจคร้านเมื่อจับข้อยกเว้นและทำสิ่งนี้:
try { someComplicatedIOFunction(); // may throw IOException someComplicatedParsingFunction(); // may throw ParsingException someComplicatedSecurityFunction(); // may throw SecurityException // phew, made it all the way } catch (Exception e) { // I'll just catch all exceptions handleError(); // with one generic handler! }
อย่าทำเช่นนี้ ในเกือบทุกกรณี ไม่เหมาะสมที่จะตรวจจับ Exception
หรือ Throwable
ทั่วไป (ไม่ควร Throwable
เนื่องจากมี Error
ยกเว้นข้อผิดพลาด) เป็นอันตรายเพราะหมายความว่าข้อยกเว้นที่คุณไม่คาดคิด (รวมถึงข้อยกเว้นรันไทม์เช่น ClassCastException
) ถูกจับในการจัดการข้อผิดพลาดระดับแอป มันปิดบังคุณสมบัติการจัดการความล้มเหลวของโค้ดของคุณ ซึ่งหมายความว่าหากมีคนเพิ่มข้อยกเว้นประเภทใหม่ในโค้ดที่คุณกำลังเรียก คอมไพเลอร์จะไม่ชี้ให้เห็นว่าคุณต้องจัดการกับข้อผิดพลาดในลักษณะที่แตกต่างออกไป ในกรณีส่วนใหญ่ คุณไม่ควรจัดการกับข้อยกเว้นประเภทต่างๆ ในลักษณะเดียวกัน
ข้อยกเว้นที่พบได้ยากสำหรับกฎนี้คือโค้ดทดสอบและโค้ดระดับบนสุด ซึ่งคุณต้องการตรวจจับข้อผิดพลาดทุกประเภท (เพื่อป้องกันไม่ให้แสดงใน UI หรือเพื่อให้งานแบทช์ทำงานต่อไป) ในกรณีเหล่านี้ คุณอาจตรวจพบ Exception
ทั่วไป (หรือ Throwable
) และจัดการข้อผิดพลาดอย่างเหมาะสม คิดให้รอบคอบก่อนที่จะทำสิ่งนี้ และใส่ความคิดเห็นที่อธิบายว่าเหตุใดจึงปลอดภัยในบริบทนี้
ทางเลือกในการจับข้อยกเว้นทั่วไป:
- จับข้อยกเว้นแต่ละรายการแยกกันโดยเป็นส่วนหนึ่งของบล็อก multi-catch เช่น
try { ... } catch (ClassNotFoundException | NoSuchMethodException e) { ... }
- ปรับโครงสร้างโค้ดของคุณใหม่เพื่อให้มีการจัดการข้อผิดพลาดที่ละเอียดยิ่งขึ้น พร้อมบล็อกการลองหลายอัน แยก IO ออกจากการแยกวิเคราะห์ และจัดการข้อผิดพลาดแยกกันในแต่ละกรณี
- โยนข้อยกเว้นอีกครั้ง หลายครั้งคุณไม่จำเป็นต้องจับข้อยกเว้นในระดับนี้อยู่แล้ว เพียงแค่ปล่อยให้วิธีการโยนมัน
จำไว้ว่าข้อยกเว้นคือเพื่อนของคุณ! เมื่อคอมไพเลอร์บ่นว่าคุณไม่ได้รับข้อยกเว้นอย่าทำหน้าบึ้ง ยิ้ม! คอมไพเลอร์ช่วยให้คุณตรวจจับปัญหารันไทม์ในโค้ดได้ง่ายขึ้น
อย่าใช้ตัวสุดท้าย
Finalizers เป็นวิธีการหนึ่งที่จะทำให้โค้ดทำงานเมื่อวัตถุถูกรวบรวมเป็นขยะ แม้ว่า Finalizers จะมีประโยชน์สำหรับการล้างข้อมูล (โดยเฉพาะทรัพยากรภายนอก) ไม่มีการรับประกันว่าจะมีการเรียก Finalizer เมื่อใด (หรือแม้แต่จะถูกเรียกเลย)
Android ไม่ได้ใช้ตัวสุดท้าย ในกรณีส่วนใหญ่ คุณสามารถใช้การจัดการข้อยกเว้นที่ดีแทนได้ หากคุณต้องการ Finalizer จริงๆ ให้กำหนดวิธี close()
(หรือสิ่งที่คล้ายคลึงกัน) และจัดทำเอกสารว่าเมื่อใดจำเป็นต้องเรียกใช้เมธอดนั้น (ดูตัวอย่างที่ InputStream ) ในกรณีนี้ เหมาะสมแต่ไม่จำเป็นต้องพิมพ์ข้อความบันทึกสั้นๆ จาก Finalizer ตราบใดที่ไม่คาดว่าจะท่วมบันทึก
นำเข้าครบทุกคุณสมบัติ
เมื่อคุณต้องการใช้ class Bar
จาก package foo
มีสองวิธีในการนำเข้า:
-
import foo.*;
อาจลดจำนวนใบแจ้งยอดการนำเข้า
-
import foo.Bar;
ทำให้ชัดเจนว่ามีการใช้คลาสใดและผู้ดูแลสามารถอ่านโค้ดได้ง่ายขึ้น
ใช้ import foo.Bar;
สำหรับการนำเข้ารหัส Android ทั้งหมด มีข้อยกเว้นที่ชัดเจนสำหรับไลบรารีมาตรฐาน Java ( java.util.*
, java.io.*
ฯลฯ ) และโค้ดทดสอบหน่วย ( junit.framework.*
)
กฎของไลบรารี Java
มีข้อตกลงในการใช้ไลบรารีและเครื่องมือ Java ของ Android ในบางกรณี แบบแผนมีการเปลี่ยนแปลงในลักษณะที่สำคัญและโค้ดที่เก่ากว่าอาจใช้รูปแบบหรือไลบรารีที่เลิกใช้แล้ว เมื่อทำงานกับโค้ดดังกล่าว คุณสามารถใช้สไตล์ที่มีอยู่ต่อไปได้ เมื่อสร้างส่วนประกอบใหม่ อย่าใช้ไลบรารีที่เลิกใช้แล้ว
กฎสไตล์ Java
ใช้ความคิดเห็นมาตรฐาน Javadoc
ทุกไฟล์ควรมีข้อความลิขสิทธิ์ที่ด้านบน ตามด้วยคำสั่งแพ็คเกจและการนำเข้า (แต่ละบล็อกคั่นด้วยบรรทัดว่าง) และสุดท้ายคือการประกาศคลาสหรืออินเทอร์เฟซ ในความคิดเห็น Javadoc ให้อธิบายว่าคลาสหรืออินเตอร์เฟสทำอะไร
/* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.foo; import android.os.Blah; import android.view.Yada; import java.sql.ResultSet; import java.sql.SQLException; /** * Does X and Y and provides an abstraction for Z. */ public class Foo { ... }
ทุกคลาสและเมธอดสาธารณะที่ไม่สำคัญที่คุณเขียน ต้อง มีความคิดเห็น Javadoc อย่างน้อยหนึ่งประโยคที่อธิบายว่าคลาสหรือเมธอดทำอะไร ประโยคนี้ควรเริ่มต้นด้วยกริยาอธิบายบุคคลที่สาม
ตัวอย่าง
/** Returns the correctly rounded positive square root of a double value. */ static double sqrt(double a) { ... }
หรือ
/** * Constructs a new String by converting the specified array of * bytes using the platform's default character encoding. */ public String(byte[] bytes) { ... }
คุณไม่จำเป็นต้องเขียน Javadoc สำหรับวิธีการรับและตั้งค่าเล็กน้อย เช่น setFoo()
หาก Javadoc ทั้งหมดของคุณบอกว่า "sets Foo" หากวิธีการทำบางสิ่งที่ซับซ้อนมากขึ้น (เช่น การบังคับใช้ข้อจำกัดหรือมีผลข้างเคียงที่สำคัญ) คุณต้องจัดทำเป็นเอกสาร หากไม่ชัดเจนว่าคุณสมบัติ "Foo" หมายถึงอะไร คุณควรจัดทำเป็นเอกสาร
ทุกวิธีที่คุณเขียน ทั้งแบบสาธารณะและแบบอื่นๆ จะได้รับประโยชน์จาก Javadoc เมธอดสาธารณะเป็นส่วนหนึ่งของ API ดังนั้นจึงต้องใช้ Javadoc Android ไม่ได้บังคับใช้รูปแบบเฉพาะสำหรับการเขียนความคิดเห็น Javadoc แต่คุณควรทำตามคำแนะนำใน วิธีเขียนความคิดเห็นเกี่ยวกับเอกสารสำหรับเครื่องมือ Javadoc
เขียนวิธีสั้นๆ
เมื่อทำได้ ให้ใช้วิธีเล็กและเน้น เราทราบดีว่าบางครั้งวิธีการแบบยาวก็เหมาะสม ดังนั้นจึงไม่มีการจำกัดความยาวของวิธีแบบตายตัว หากวิธีการใดเกิน 40 บรรทัด ให้คิดดูว่าสามารถแบ่งได้โดยไม่ทำลายโครงสร้างของโปรแกรมหรือไม่
กำหนดฟิลด์ในตำแหน่งมาตรฐาน
กำหนดฟิลด์ที่ด้านบนสุดของไฟล์หรือก่อนเมธอดที่ใช้
จำกัดขอบเขตตัวแปร
รักษาขอบเขตของตัวแปรท้องถิ่นให้น้อยที่สุด ซึ่งจะช่วยเพิ่มความสามารถในการอ่านและบำรุงรักษาโค้ดของคุณ และลดโอกาสเกิดข้อผิดพลาด ประกาศตัวแปรแต่ละตัวในบล็อกชั้นในสุดที่ล้อมรอบการใช้งานตัวแปรทั้งหมด
ประกาศตัวแปรท้องถิ่น ณ จุดที่ใช้งานครั้งแรก เกือบทุกการประกาศตัวแปรโลคัลควรมีตัวเริ่มต้น หากคุณยังไม่มีข้อมูลเพียงพอที่จะเริ่มต้นตัวแปรอย่างสมเหตุสมผล ให้เลื่อนการประกาศออกไปจนกว่าคุณจะทำ
ข้อยกเว้นคือคำสั่ง try-catch หากตัวแปรเริ่มต้นด้วยค่าส่งคืนของวิธีการที่ส่งข้อยกเว้นที่ตรวจสอบแล้ว ตัวแปรนั้นจะต้องได้รับการเตรียมข้อมูลเบื้องต้นภายในบล็อกการลอง หากต้องใช้ค่านอกบล็อก try จะต้องประกาศก่อนบล็อก try ซึ่งยังไม่สามารถเริ่มต้นได้อย่างสมเหตุสมผล:
// Instantiate class cl, which represents some sort of Set Set s = null; try { s = (Set) cl.newInstance(); } catch(IllegalAccessException e) { throw new IllegalArgumentException(cl + " not accessible"); } catch(InstantiationException e) { throw new IllegalArgumentException(cl + " not instantiable"); } // Exercise the set s.addAll(Arrays.asList(args));
อย่างไรก็ตาม คุณสามารถหลีกเลี่ยงกรณีนี้ด้วยการห่อหุ้มบล็อก try-catch ด้วยวิธีการ:
Set createSet(Class cl) { // Instantiate class cl, which represents some sort of Set try { return (Set) cl.newInstance(); } catch(IllegalAccessException e) { throw new IllegalArgumentException(cl + " not accessible"); } catch(InstantiationException e) { throw new IllegalArgumentException(cl + " not instantiable"); } } ... // Exercise the set Set s = createSet(cl); s.addAll(Arrays.asList(args));
ประกาศตัวแปรลูปในคำสั่ง for เอง เว้นแต่จะมีเหตุผลที่น่าสนใจให้ทำอย่างอื่น:
for (int i = 0; i < n; i++) { doSomething(i); }
และ
for (Iterator i = c.iterator(); i.hasNext(); ) { doSomethingElse(i.next()); }
คำสั่งนำเข้าคำสั่ง
ลำดับของใบแจ้งการนำเข้าคือ:
- การนำเข้า Android
- การนำเข้าจากบุคคลที่สาม (
com
,junit
,net
,org
) -
java
และjavax
เพื่อให้ตรงกับการตั้งค่า IDE ทุกประการ การนำเข้าควรเป็น:
- ตามตัวอักษรในแต่ละกลุ่ม โดยมีตัวพิมพ์ใหญ่ก่อนตัวพิมพ์เล็ก (เช่น Z ก่อน a)
- คั่นด้วยบรรทัดว่างระหว่างแต่ละกลุ่มหลัก (
android
,com
,junit
,net
,org
,java
,javax
)
เดิมทีไม่มีข้อกำหนดด้านรูปแบบในการสั่งซื้อ หมายความว่า IDE มักเปลี่ยนการสั่งซื้อหรือนักพัฒนา IDE ต้องปิดใช้งานคุณลักษณะการจัดการการนำเข้าอัตโนมัติและดูแลรักษาการนำเข้าด้วยตนเอง นี่ถือว่าแย่ เมื่อถามถึงสไตล์ Java สไตล์ที่ต้องการจะแตกต่างกันออกไปอย่างมาก และมาถึง Android ที่ต้องการเพียงแค่ "เลือกลำดับและสม่ำเสมอ" ดังนั้นเราจึงเลือกสไตล์ อัปเดตคู่มือสไตล์ และทำให้ IDE ปฏิบัติตาม เราคาดว่าในขณะที่ผู้ใช้ IDE ทำงานกับโค้ด การนำเข้าในแพ็คเกจทั้งหมดจะตรงกับรูปแบบนี้โดยไม่ต้องใช้ความพยายามด้านวิศวกรรมเพิ่มเติม
เราเลือกรูปแบบนี้เพื่อให้:
- การนำเข้าที่ผู้คนต้องการดูก่อนมักจะอยู่ด้านบนสุด (
android
) - การนำเข้าที่ผู้คนต้องการดูอย่างน้อยมักจะอยู่ด้านล่าง (
java
) - มนุษย์สามารถทำตามสไตล์ได้อย่างง่ายดาย
- IDE สามารถทำตามสไตล์ได้
ใส่การนำเข้าแบบคงที่เหนือการนำเข้าอื่นๆ ทั้งหมดที่สั่งซื้อในลักษณะเดียวกับการนำเข้าปกติ
ใช้ช่องว่างสำหรับการเยื้อง
เราใช้การเว้นวรรคสี่ (4) ครั้งสำหรับบล็อกและไม่ใช้แท็บ เมื่อมีข้อสงสัย ให้สอดคล้องกับรหัสโดยรอบ
เราใช้การเว้นวรรคแปด (8) สำหรับการตัดบรรทัด รวมถึงการเรียกใช้ฟังก์ชันและการกำหนด
ที่แนะนำ
Instrument i = someLongExpression(that, wouldNotFit, on, one, line);
ไม่แนะนำ
Instrument i = someLongExpression(that, wouldNotFit, on, one, line);
ปฏิบัติตามข้อตกลงการตั้งชื่อสนาม
- ชื่อฟิลด์ที่ไม่เป็นสาธารณะและไม่คงที่เริ่มต้นด้วย
m
- ชื่อฟิลด์คงที่เริ่มต้นด้วย
s
- ช่องอื่นๆ ขึ้นต้นด้วยอักษรตัวพิมพ์เล็ก
- ฟิลด์สุดท้ายแบบคงที่ (ค่าคงที่ ไม่เปลี่ยนรูปแบบอย่างล้ำลึก) คือ
ALL_CAPS_WITH_UNDERSCORES
ตัวอย่างเช่น:
public class MyClass { public static final int SOME_CONSTANT = 42; public int publicField; private static MyClass sSingleton; int mPackagePrivate; private int mPrivate; protected int mProtected; }
ใช้เหล็กค้ำยันมาตรฐาน
ใส่เครื่องหมายปีกกาในบรรทัดเดียวกับโค้ดที่อยู่ข้างหน้า ไม่ใช่ในบรรทัดของตัวเอง:
class MyClass { int func() { if (something) { // ... } else if (somethingElse) { // ... } else { // ... } } }
เราต้องการวงเล็บปีกการอบคำสั่งสำหรับเงื่อนไข ข้อยกเว้น: หากเงื่อนไขทั้งหมด (เงื่อนไขและเนื้อหา) ตรงกันในบรรทัดเดียว คุณอาจ (แต่ไม่จำเป็นต้อง) ใส่ทั้งหมดลงในบรรทัดเดียว ตัวอย่างเช่น สิ่งนี้เป็นที่ยอมรับ:
if (condition) { body(); }
และสิ่งนี้เป็นที่ยอมรับ:
if (condition) body();
แต่สิ่งนี้ไม่เป็นที่ยอมรับ:
if (condition) body(); // bad!
จำกัดความยาวสาย
ข้อความแต่ละบรรทัดในโค้ดของคุณควรมีความยาวไม่เกิน 100 อักขระ แม้ว่าจะมีการพูดคุยกันมากมายในกฎนี้ การตัดสินใจยังคงมีอักขระสูงสุด 100 ตัว โดยมีข้อยกเว้นดังต่อไปนี้ :
- หากบรรทัดความคิดเห็นมีคำสั่งตัวอย่างหรือ URL ตามตัวอักษรที่ยาวกว่า 100 อักขระ บรรทัดนั้นอาจยาวเกิน 100 อักขระเพื่อให้ง่ายต่อการตัดและวาง
- บรรทัดการนำเข้าอาจเกินขีดจำกัดเนื่องจากมนุษย์ไม่ค่อยเห็นบรรทัดเหล่านี้ (สิ่งนี้ยังช่วยลดความยุ่งยากในการเขียนเครื่องมือด้วย)
ใช้คำอธิบายประกอบ Java มาตรฐาน
หมายเหตุควรนำหน้าตัวแก้ไขอื่นๆ สำหรับองค์ประกอบภาษาเดียวกัน คำอธิบายประกอบตัวทำเครื่องหมายอย่างง่าย (เช่น @Override
) สามารถแสดงรายการในบรรทัดเดียวกันกับองค์ประกอบภาษา หากมีคำอธิบายประกอบหลายรายการ หรือคำอธิบายประกอบแบบกำหนดพารามิเตอร์ ให้แสดงรายการหนึ่งรายการต่อบรรทัดตามลำดับตัวอักษร
แนวทางปฏิบัติมาตรฐานของ Android สำหรับคำอธิบายประกอบที่กำหนดไว้ล่วงหน้าสามรายการใน Java ได้แก่:
- ใช้คำอธิบายประกอบ
@Deprecated
เมื่อใดก็ตามที่ไม่สนับสนุนการใช้องค์ประกอบที่มีคำอธิบายประกอบ หากคุณใช้คำอธิบายประกอบ@Deprecated
คุณต้องมีแท็ก@deprecated
Javadoc ด้วย และควรตั้งชื่อการใช้งานทางเลือก นอกจากนี้ โปรดจำไว้ว่าเมธอด@Deprecated
ยังคงใช้งาน ได้ หากคุณเห็นโค้ดเก่าที่มีแท็ก@deprecated
Javadoc ให้เพิ่มคำอธิบายประกอบ@Deprecated
- ใช้คำอธิบายประกอบ
@Override
เมื่อใดก็ตามที่เมธอดแทนที่การประกาศหรือการใช้งานจากซูเปอร์คลาส ตัวอย่างเช่น หากคุณใช้แท็ก@inheritdocs
Javadoc และได้รับมาจากคลาส (ไม่ใช่อินเทอร์เฟซ) คุณต้องใส่หมายเหตุว่าเมธอดจะแทนที่เมธอดของคลาสพาเรนต์ - ใช้คำอธิบายประกอบ
@SuppressWarnings
เฉพาะในสถานการณ์ที่ไม่สามารถขจัดคำเตือนได้ หากคำเตือนผ่านการทดสอบ "เป็นไปไม่ได้ที่จะกำจัด" นี้ ต้อง ใช้คำอธิบายประกอบ@SuppressWarnings
เพื่อให้แน่ใจว่าคำเตือนทั้งหมดสะท้อนถึงปัญหาจริงในโค้ดเมื่อจำเป็นต้องมีคำอธิบายประกอบ
@SuppressWarnings
จะต้องนำหน้าด้วยความคิดเห็นTODO
ที่อธิบายเงื่อนไข "เป็นไปไม่ได้ที่จะกำจัด" โดยปกติสิ่งนี้จะระบุคลาสที่ละเมิดซึ่งมีอินเทอร์เฟซที่น่าอึดอัดใจ ตัวอย่างเช่น:// TODO: The third-party class com.third.useful.Utility.rotate() needs generics @SuppressWarnings("generic-cast") List<String> blix = Utility.rotate(blax);
เมื่อจำเป็นต้องมีคำอธิบายประกอบ
@SuppressWarnings
ให้ปรับโครงสร้างโค้ดใหม่เพื่อแยกองค์ประกอบซอฟต์แวร์ที่ใช้คำอธิบายประกอบ
ใช้คำย่อเป็นคำ
ใช้ตัวย่อและตัวย่อเป็นคำในการตั้งชื่อตัวแปร เมธอด และคลาสเพื่อทำให้ชื่ออ่านง่ายขึ้น:
ดี | แย่ |
---|---|
XmlHttpRequest | XMLHTTPRequest |
รับรหัสลูกค้า | รับรหัสลูกค้า |
คลาส Html | คลาส HTML |
URL สตริง | URL สตริง |
id ยาว | ID ยาว |
เนื่องจากทั้ง JDK และรหัสฐานของ Android ไม่สอดคล้องกันเกี่ยวกับตัวย่อ จึงแทบจะเป็นไปไม่ได้เลยที่จะสอดคล้องกับรหัสโดยรอบ ดังนั้น ให้ถือว่าคำย่อเป็นคำเสมอ
ใช้ความคิดเห็น TODO
ใช้ความคิดเห็นของ TODO
สำหรับโค้ดชั่วคราว วิธีแก้ปัญหาระยะสั้น หรือดีพอแต่ยังไม่สมบูรณ์ ความคิดเห็นเหล่านี้ควรรวมสตริง TODO
ไว้ในตัวพิมพ์ใหญ่ทั้งหมด ตามด้วยโคลอน:
// TODO: Remove this code after the UrlTable2 has been checked in.
และ
// TODO: Change this to use a flag instead of a constant.
หาก TODO
ของคุณอยู่ในรูปแบบ "ในอนาคตจะทำอะไรบางอย่าง" ให้ตรวจสอบว่าคุณได้ใส่วันที่เฉพาะ ("แก้ไขภายในเดือนพฤศจิกายน 2548") หรือเหตุการณ์เฉพาะ ("ลบโค้ดนี้หลังจากที่ตัวผสมการผลิตทั้งหมดเข้าใจโปรโตคอล V7" ).
บันทึกเท่าที่จำเป็น
แม้ว่าการบันทึกจะมีความจำเป็น แต่ก็ส่งผลกระทบในทางลบต่อประสิทธิภาพและสูญเสียประโยชน์ไปหากไม่อธิบายอย่างถี่ถ้วน สิ่งอำนวยความสะดวกการบันทึกมีห้าระดับที่แตกต่างกันของการบันทึก:
-
ERROR
: ใช้เมื่อมีบางสิ่งที่ร้ายแรงเกิดขึ้น นั่นคือ บางสิ่งจะมีผลที่ผู้ใช้มองเห็นได้ และจะไม่สามารถกู้คืนได้หากไม่ได้ลบข้อมูลบางส่วน ถอนการติดตั้งแอป เช็ดพาร์ทิชันข้อมูล หรือรีเฟรชอุปกรณ์ทั้งหมด (หรือแย่กว่านั้น) ระดับนี้จะถูกบันทึกไว้เสมอ ปัญหาที่พิสูจน์ความถูกต้องของการบันทึกบางอย่างที่ระดับERROR
คือตัวเลือกที่ดีที่จะรายงานไปยังเซิร์ฟเวอร์การรวบรวมสถิติ -
WARNING
: ใช้เมื่อมีบางสิ่งที่ร้ายแรงและไม่คาดคิดเกิดขึ้น นั่นคือ สิ่งที่จะมีผลที่ผู้ใช้มองเห็นได้ แต่มีแนวโน้มที่จะสามารถกู้คืนได้โดยไม่สูญเสียข้อมูลโดยดำเนินการบางอย่างที่ชัดเจน ตั้งแต่การรอหรือรีสตาร์ทแอปไปจนถึงการดาวน์โหลดใหม่ แอปเวอร์ชันใหม่หรือรีบูตอุปกรณ์ ระดับนี้จะถูกบันทึกไว้เสมอ ปัญหาที่ปรับการบันทึกที่ระดับWARNING
อาจได้รับการพิจารณาสำหรับการรายงานไปยังเซิร์ฟเวอร์การรวบรวมสถิติ -
INFORMATIVE
: ใช้เพื่อสังเกตว่าสิ่งที่น่าสนใจเกิดขึ้น นั่นคือ เมื่อตรวจพบสถานการณ์ที่น่าจะส่งผลกระทบในวงกว้าง แม้ว่าจะไม่ใช่ข้อผิดพลาดเสมอไป เงื่อนไขดังกล่าวควรได้รับการบันทึกโดยโมดูลที่เชื่อว่าเชื่อถือได้มากที่สุดในโดเมนนั้น (เพื่อหลีกเลี่ยงการบันทึกซ้ำโดยส่วนประกอบที่ไม่ได้รับอนุญาต) ระดับนี้จะถูกบันทึกไว้เสมอ -
DEBUG
: ใช้เพื่อให้ทราบเพิ่มเติมว่าเกิดอะไรขึ้นบนอุปกรณ์ที่อาจเกี่ยวข้องกับการตรวจสอบและดีบักพฤติกรรมที่ไม่คาดคิด บันทึกเฉพาะสิ่งที่จำเป็นในการรวบรวมข้อมูลที่เพียงพอเกี่ยวกับสิ่งที่เกิดขึ้นกับส่วนประกอบของคุณ หากบันทึกการดีบักของคุณมีอำนาจเหนือบันทึก คุณควรใช้การบันทึกแบบละเอียดระดับนี้ถูกบันทึกแม้ในรุ่นบิลด์ และจำเป็นต้องล้อมรอบด้วย
if (LOCAL_LOG)
หรือif LOCAL_LOGD)
โดยที่LOCAL_LOG[D]
ถูกกำหนดไว้ในคลาสหรือองค์ประกอบย่อยของคุณ ดังนั้นจึงมีความเป็นไปได้ที่จะปิดการใช้งานการบันทึกดังกล่าวทั้งหมด . ดังนั้น จะต้องไม่มีตรรกะที่ใช้งานอยู่ในบล็อกif (LOCAL_LOG)
การสร้างสตริงทั้งหมดสำหรับบันทึกจะต้องอยู่ภายในบล็อกif (LOCAL_LOG)
ด้วย อย่าปรับโครงสร้างการเรียกล็อกเอาต์ใหม่เป็นการเรียกเมธอด หากจะทำให้การสร้างสตริงเกิดขึ้นนอกบล็อกif (LOCAL_LOG)
มีโค้ดบางตัวที่ยังคงบอกว่า
if (localLOGV)
ถือว่ายอมรับได้เช่นกัน แม้ว่าชื่อจะไม่เป็นมาตรฐานก็ตาม -
VERBOSE
: ใช้สำหรับอย่างอื่น ระดับนี้เข้าสู่ระบบเฉพาะในบิลด์ดีบักและควรล้อมรอบด้วยบล็อกif (LOCAL_LOGV)
(หรือเทียบเท่า) เพื่อให้สามารถคอมไพล์ได้โดยค่าเริ่มต้น การสร้างสตริงใดๆ จะถูกดึงออกจากบิลด์ที่วางจำหน่าย และจำเป็นต้องปรากฏภายในบล็อกif (LOCAL_LOGV)
หมายเหตุ
- ภายในโมดูลที่กำหนด นอกเหนือจากที่ระดับ
VERBOSE
ควรรายงานข้อผิดพลาดเพียงครั้งเดียวหากเป็นไปได้ ภายในสายเดียวของการเรียกใช้ฟังก์ชันภายในโมดูล เฉพาะฟังก์ชันที่อยู่ด้านในสุดเท่านั้นที่ควรส่งคืนข้อผิดพลาด และผู้เรียกใช้ในโมดูลเดียวกันควรเพิ่มการบันทึกบางอย่างเท่านั้นหากนั่นช่วยแยกปัญหาได้อย่างมาก - ในกลุ่มโมดูล นอกเหนือจากที่ระดับ
VERBOSE
เมื่อโมดูลระดับล่างตรวจพบข้อมูลที่ไม่ถูกต้องซึ่งมาจากโมดูลระดับที่สูงกว่า โมดูลระดับล่างควรบันทึกสถานการณ์นี้ลงในบันทึกDEBUG
เท่านั้น และเฉพาะในกรณีที่มีการบันทึก ข้อมูลที่ไม่สามารถใช้ได้กับผู้โทร โดยเฉพาะอย่างยิ่ง ไม่จำเป็นต้องบันทึกสถานการณ์ที่มีการส่งข้อยกเว้น (ข้อยกเว้นควรมีข้อมูลที่เกี่ยวข้องทั้งหมด) หรือในกรณีที่มีข้อมูลเดียวที่บันทึกอยู่ในรหัสข้อผิดพลาด นี่เป็นสิ่งสำคัญอย่างยิ่งในการโต้ตอบระหว่างเฟรมเวิร์กและแอพ และเงื่อนไขที่เกิดจากแอพของบุคคลที่สามที่ได้รับการจัดการอย่างเหมาะสมโดยเฟรมเวิร์กไม่ควรทริกเกอร์การบันทึกที่สูงกว่าระดับDEBUG
สถานการณ์เดียวที่ควรทริกเกอร์การบันทึกที่ระดับINFORMATIVE
หรือสูงกว่าคือเมื่อโมดูลหรือแอปตรวจพบข้อผิดพลาดที่ระดับของตนเองหรือมาจากระดับที่ต่ำกว่า - เมื่อเงื่อนไขที่ปกติจะปรับการบันทึกบางอย่างมีแนวโน้มที่จะเกิดขึ้นหลายครั้ง อาจเป็นความคิดที่ดีที่จะใช้กลไกการจำกัดอัตราเพื่อป้องกันการล้นบันทึกด้วยสำเนาที่ซ้ำกันจำนวนมากของข้อมูลเดียวกัน (หรือคล้ายกันมาก)
- การสูญเสียการเชื่อมต่อเครือข่ายถือเป็นเรื่องปกติและเป็นไปตามคาด และไม่ควรบันทึกโดยไม่จำเป็น การสูญเสียการเชื่อมต่อเครือข่ายที่มีผลกระทบภายในแอปควรได้รับการบันทึกที่ระดับ
DEBUG
หรือVERBOSE
(ขึ้นอยู่กับว่าผลที่ตามมานั้นร้ายแรงพอและไม่คาดคิดมากพอที่จะบันทึกในบิลด์รุ่นหรือไม่) - การมีระบบไฟล์เต็มรูปแบบบนระบบไฟล์ที่เข้าถึงได้หรือในนามของแอปของบุคคลที่สามไม่ควรถูกบันทึกในระดับที่สูงกว่าข้อมูล
- ข้อมูลที่ไม่ถูกต้องที่มาจากแหล่งที่ไม่น่าเชื่อถือใดๆ (รวมถึงไฟล์ใดๆ บนที่จัดเก็บข้อมูลที่ใช้ร่วมกัน หรือข้อมูลที่มาจากการเชื่อมต่อเครือข่าย) ถือเป็นข้อมูลที่คาดหวังและไม่ควรทริกเกอร์การบันทึกใดๆ ในระดับที่สูงกว่า
DEBUG
เมื่อตรวจพบว่าไม่ถูกต้อง (และแม้กระทั่งการบันทึก ควรจะจำกัดให้มากที่สุด) - เมื่อใช้กับวัตถุ
String
ตัวดำเนินการ+
จะสร้างอินสแตนซ์StringBuilder
โดยปริยายด้วยขนาดบัฟเฟอร์เริ่มต้น (16 อักขระ) และวัตถุString
ชั่วคราวอื่นๆ ที่อาจเป็นไปได้ ดังนั้นการสร้างวัตถุStringBuilder
อย่างชัดเจนจึงไม่แพงไปกว่าการใช้ตัวดำเนินการ+
เริ่มต้น (และอาจมีประสิทธิภาพมากกว่ามาก) โปรดทราบว่าโค้ดที่เรียกLog.v()
ได้รับการคอมไพล์และดำเนินการบนบิลด์ที่วางจำหน่าย รวมถึงการสร้างสตริง แม้ว่าจะไม่ได้อ่านบันทึกก็ตาม - การบันทึกใดๆ ที่ผู้อื่นควรอ่านและพร้อมใช้งานในรุ่นรุ่นต่างๆ ควรมีความกระชับและไม่คลุมเครือ และควรเข้าใจได้ ซึ่งรวมถึงการบันทึกทั้งหมดจนถึงระดับ
DEBUG
- หากเป็นไปได้ ให้เข้าสู่ระบบในบรรทัดเดียว ความยาวบรรทัดที่ยอมรับได้ไม่เกิน 80 หรือ 100 อักขระ หลีกเลี่ยงความยาวที่ยาวเกินประมาณ 130 หรือ 160 อักขระ (รวมทั้งความยาวของแท็ก) ถ้าเป็นไปได้
- หากการบันทึกรายงานสำเร็จ อย่าใช้ในระดับที่สูงกว่า
VERBOSE
- หากคุณกำลังใช้การบันทึกชั่วคราวเพื่อวินิจฉัยปัญหาที่ยากต่อการทำซ้ำ ให้เก็บไว้ที่ระดับ
DEBUG
หรือVERBOSE
และปิดล้อมด้วย if บล็อกที่อนุญาตให้ปิดการใช้งานในเวลาคอมไพล์ - ระวังเรื่องความปลอดภัยรั่วไหลผ่านบันทึก หลีกเลี่ยงการบันทึกข้อมูลส่วนตัว โดยเฉพาะอย่างยิ่ง หลีกเลี่ยงการบันทึกข้อมูลเกี่ยวกับเนื้อหาที่ได้รับการคุ้มครอง นี่เป็นสิ่งสำคัญโดยเฉพาะอย่างยิ่งเมื่อเขียนโค้ดเฟรมเวิร์ก เนื่องจากไม่ใช่เรื่องง่ายที่จะทราบล่วงหน้าว่าอะไรจะเป็นและจะไม่เป็นข้อมูลส่วนตัวหรือเนื้อหาที่ได้รับการคุ้มครอง
- ห้ามใช้
System.out.println()
(หรือprintf()
สำหรับโค้ดเนทีฟ)System.out
และSystem.err
ถูกเปลี่ยนเส้นทางไปที่/dev/null
ดังนั้นคำสั่งการพิมพ์ของคุณจึงไม่มีผลที่มองเห็นได้ อย่างไรก็ตาม การสร้างสตริงทั้งหมดที่เกิดขึ้นสำหรับการเรียกเหล่านี้ยังคงถูกดำเนินการ - กฎทองของการบันทึกคือ บันทึกของคุณอาจไม่ผลักบันทึกอื่น ๆ ออกจากบัฟเฟอร์โดยไม่จำเป็น เช่นเดียวกับที่ผู้อื่นอาจไม่ผลักบันทึกของคุณออกไป
กฎของสไตล์ Javatests
ปฏิบัติตามข้อตกลงการตั้งชื่อวิธีการทดสอบและใช้ขีดล่างเพื่อแยกสิ่งที่กำลังทดสอบออกจากกรณีเฉพาะที่กำลังทดสอบ สไตล์นี้ทำให้ง่ายต่อการดูว่ากรณีใดกำลังถูกทดสอบ ตัวอย่างเช่น:
testMethod_specificCase1 testMethod_specificCase2 void testIsDistinguishable_protanopia() { ColorMatcher colorMatcher = new ColorMatcher(PROTANOPIA) assertFalse(colorMatcher.isDistinguishable(Color.RED, Color.BLACK)) assertTrue(colorMatcher.isDistinguishable(Color.X, Color.Y)) }