สไตล์โค้ดในหน้านี้เป็นกฎที่เข้มงวดสำหรับการส่งโค้ด Java ให้กับ Android Open Source Project (AOSP) การสนับสนุนแพลตฟอร์ม Android ที่ไม่เป็นไปตามกฎเหล่านี้โดยทั่วไป จะไม่ได้รับการยอมรับ เราทราบดีว่าโค้ดที่มีอยู่บางส่วนไม่เป็นไปตามกฎเหล่านี้ แต่เราคาดว่าโค้ดใหม่ทั้งหมดจะสอดคล้อง ดูการ เข้ารหัสด้วยความเคารพ สำหรับตัวอย่างคำศัพท์ที่จะใช้และหลีกเลี่ยงสำหรับระบบนิเวศที่ครอบคลุมมากขึ้น
คงเส้นคงวา
กฎที่ง่ายที่สุดข้อหนึ่งคือต้องสอดคล้องกัน หากคุณกำลังแก้ไขโค้ด ให้ใช้เวลาสักครู่เพื่อดูโค้ดที่อยู่รอบๆ และกำหนดรูปแบบของโค้ด หากรหัสนั้นใช้ช่องว่างรอบคำสั่ง if
คุณควรทำเช่นกัน หากความคิดเห็นของโค้ดมีกล่องรูปดาวเล็กๆ ล้อมรอบ ให้แสดงความคิดเห็นของคุณให้มีกล่องรูปดาวเล็กๆ รอบๆ ด้วยเช่นกัน
ประเด็นของการมีแนวทางเกี่ยวกับสไตล์คือการมีคำศัพท์ทั่วไปเกี่ยวกับการเขียนโค้ด เพื่อให้ผู้อ่านสามารถจดจ่อกับสิ่งที่คุณพูด แทนที่จะสนใจวิธีที่คุณพูด เรานำเสนอกฎสไตล์สากลที่นี่เพื่อให้คุณรู้คำศัพท์ แต่สไตล์ท้องถิ่นก็สำคัญเช่นกัน หากโค้ดที่คุณเพิ่มไปยังไฟล์ดูแตกต่างอย่างมากจากโค้ดที่มีอยู่รอบ ๆ ไฟล์ จะทำให้ผู้อ่านไม่สามารถอ่านได้ทันท่วงทีเมื่ออ่าน พยายามหลีกเลี่ยงสิ่งนี้
กฎของภาษาจาวา
Android ปฏิบัติตามข้อตกลงการเข้ารหัส Java มาตรฐานพร้อมกฎเพิ่มเติมที่อธิบายไว้ด้านล่าง
อย่าเพิกเฉยต่อข้อยกเว้น
อาจเป็นเรื่องดึงดูดใจที่จะเขียนโค้ดที่ละเว้นข้อยกเว้น เช่น:
void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { } }
อย่าทำเช่นนี้ แม้ว่าคุณอาจคิดว่าโค้ดของคุณจะไม่พบเงื่อนไขข้อผิดพลาดนี้หรือคิดว่าไม่สำคัญที่จะจัดการกับมัน แต่การเพิกเฉยต่อข้อยกเว้นประเภทนี้จะสร้างเหมืองในโค้ดของคุณเพื่อให้คนอื่นเรียกใช้ในวันใดวันหนึ่ง คุณต้องจัดการกับทุกข้อยกเว้นในโค้ดของคุณอย่างมีหลักการ การจัดการเฉพาะแตกต่างกันไปขึ้นอยู่กับกรณี
" เมื่อใดก็ตามที่ใครบางคนมี catch clause ที่ว่างเปล่า พวกเขาควรจะมีความรู้สึกน่าขนลุก มีบางครั้งที่จริง ๆ แล้วเป็นสิ่งที่ถูกต้องที่ต้องทำ แต่อย่างน้อยคุณต้องคิดเกี่ยวกับมัน ใน 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
) และจัดการข้อผิดพลาดได้อย่างเหมาะสม คิดอย่างรอบคอบก่อนที่จะทำสิ่งนี้และแสดงความคิดเห็นที่อธิบายว่าเหตุใดจึงปลอดภัยในบริบทนี้
ทางเลือกอื่นในการตรวจจับข้อยกเว้นทั่วไป:
- จับข้อยกเว้นแต่ละรายการแยกจากกันโดยเป็นส่วนหนึ่งของบล็อกหลายรายการ ตัวอย่างเช่น:
try { ... } catch (ClassNotFoundException | NoSuchMethodException e) { ... }
- ปรับโครงสร้างรหัสของคุณใหม่เพื่อให้มีการจัดการข้อผิดพลาดที่ละเอียดมากขึ้น พร้อมบล็อกการลองหลายครั้ง แยก IO ออกจากการแยกวิเคราะห์ และจัดการข้อผิดพลาดแยกกันในแต่ละกรณี
- โยนข้อยกเว้นอีกครั้ง หลายครั้งที่คุณไม่จำเป็นต้องตรวจจับข้อยกเว้นในระดับนี้ ปล่อยให้เมธอดโยนทิ้งไป
จำไว้ว่าข้อยกเว้นคือเพื่อนของคุณ! เมื่อคอมไพเลอร์บ่นว่าคุณจับข้อยกเว้นไม่ได้ อย่าทำหน้าบึ้ง รอยยิ้ม! คอมไพเลอร์ช่วยให้คุณตรวจจับปัญหารันไทม์ในโค้ดของคุณได้ง่ายขึ้น
อย่าใช้ไฟนอลไลเซอร์
Finalizers เป็นวิธีการเรียกใช้โค้ดจำนวนมากเมื่อวัตถุถูกรวบรวมขยะ แม้ว่า Finalizers จะมีประโยชน์สำหรับการล้างข้อมูล (โดยเฉพาะทรัพยากรภายนอก) แต่ก็ไม่มีการรับประกันว่าจะมีการเรียก Finalizer เมื่อใด (หรือแม้แต่การเรียกใช้เลยด้วยซ้ำ)
Android ไม่ใช้ตัวสุดท้าย ในกรณีส่วนใหญ่ คุณสามารถใช้การจัดการข้อยกเว้นที่ดีแทนได้ หากคุณต้องการ Finalizer อย่างยิ่ง ให้กำหนดเมธอด close()
(หรือสิ่งที่คล้ายกัน) และจัดทำเอกสารว่าเมื่อใดที่เมธอดนั้นจำเป็นต้องเรียก (ดูตัวอย่าง InputStream ) ในกรณีนี้ เหมาะสมแต่ไม่จำเป็นต้องพิมพ์ข้อความบันทึกสั้นๆ จาก Finalizer ตราบใดที่ไม่คาดว่าจะท่วมบันทึก
การนำเข้าที่มีคุณสมบัติครบถ้วน
เมื่อคุณต้องการใช้ class Bar
จากแพ็คเกจ foo
มีสองวิธีที่เป็นไปได้ในการนำเข้า:
-
import foo.*;
อาจลดจำนวนคำสั่งการนำเข้า
-
import foo.Bar;
ทำให้ชัดเจนว่าใช้คลาสอะไร และโค้ดนั้นอ่านง่ายขึ้นสำหรับผู้ดูแล
ใช้ import foo.Bar;
สำหรับนำเข้ารหัส Android ทั้งหมด มีข้อยกเว้นอย่างชัดเจนสำหรับไลบรารีมาตรฐาน Java ( java.util.*
, java.io.*
ฯลฯ) และโค้ดทดสอบหน่วย ( junit.framework.*
)
กฎของไลบรารี Java
มีข้อกำหนดสำหรับการใช้ไลบรารีและเครื่องมือ Java ของ Android ในบางกรณี แบบแผนมีการเปลี่ยนแปลงที่สำคัญ และโค้ดที่เก่ากว่าอาจใช้รูปแบบหรือไลบรารีที่เลิกใช้แล้ว เมื่อทำงานกับโค้ดดังกล่าว คุณสามารถดำเนินการตามสไตล์ที่มีอยู่ต่อไปได้ อย่างไรก็ตาม เมื่อสร้างส่วนประกอบใหม่ ห้ามใช้ไลบรารี่ที่เลิกใช้แล้ว
กฎสไตล์ Java
ใช้ความคิดเห็นมาตรฐานของ Javadoc
ทุกไฟล์ควรมีคำชี้แจงลิขสิทธิ์ที่ด้านบน ตามด้วยคำสั่งแพ็คเกจและการนำเข้า (แต่ละบล็อกคั่นด้วยบรรทัดว่าง) และสุดท้ายคือการประกาศคลาสหรืออินเทอร์เฟซ ในข้อคิดเห็น Javadoc ให้อธิบายว่าคลาสหรืออินเตอร์เฟสทำอะไร
/* * Copyright 2023 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 สำหรับเมธอด get และ set เล็กน้อย เช่น setFoo()
หาก Javadoc ทั้งหมดของคุณบอกว่าเป็น "sets Foo" หากเมธอดทำบางสิ่งที่ซับซ้อนกว่านั้น (เช่น การบังคับใช้ข้อจำกัดหรือมีผลข้างเคียงที่สำคัญ) คุณต้องจัดทำเป็นเอกสาร หากไม่ชัดเจนว่าคำว่า "ฟู" หมายถึงอะไร คุณควรบันทึกเป็นเอกสาร
ทุกวิธีที่คุณเขียน สาธารณะหรืออื่นๆ จะได้รับประโยชน์จาก Javadoc วิธีการสาธารณะเป็นส่วนหนึ่งของ API ดังนั้นจึงต้องใช้ Javadoc Android ไม่ได้บังคับใช้รูปแบบเฉพาะสำหรับการเขียนความคิดเห็น Javadoc แต่คุณควรทำตามคำแนะนำใน วิธีเขียนความคิดเห็นในเอกสารสำหรับเครื่องมือ Javadoc
เขียนวิธีการสั้นๆ
เมื่อเป็นไปได้ ให้ใช้วิธีการเล็กๆ และมุ่งเน้น เราทราบดีว่าบางครั้งเมธอดแบบยาวก็เหมาะสม ดังนั้นจึงไม่มีการกำหนดขีดจำกัดตายตัวสำหรับความยาวของเมธอด ถ้าเมธอดยาวเกิน 40 บรรทัด ให้ลองคิดดูว่าเมธอดนั้นสามารถแยกออกได้โดยไม่ทำลายโครงสร้างของโปรแกรมหรือไม่
กำหนดเขตข้อมูลในสถานที่มาตรฐาน
กำหนดฟิลด์ที่ด้านบนสุดของไฟล์หรือทันทีก่อนเมธอดที่ใช้
จำกัดขอบเขตของตัวแปร
รักษาขอบเขตของตัวแปรโลคัลให้น้อยที่สุด สิ่งนี้จะเพิ่มความสามารถในการอ่านและการบำรุงรักษาโค้ดของคุณ และลดโอกาสที่จะเกิดข้อผิดพลาด ประกาศตัวแปรแต่ละตัวในบล็อกด้านในสุดที่ปิดการใช้งานตัวแปรทั้งหมด
ประกาศตัวแปรโลคัล ณ จุดที่ใช้ครั้งแรก การประกาศตัวแปรโลคอลเกือบทุกรายการควรมีตัวเริ่มต้น หากคุณยังไม่มีข้อมูลเพียงพอที่จะเริ่มต้นตัวแปรอย่างสมเหตุสมผล ให้เลื่อนการประกาศออกไปจนกว่าคุณจะทำ
ข้อยกเว้นคือคำสั่ง try-catch ถ้าตัวแปรถูกเริ่มต้นด้วยค่าส่งคืนของเมธอดที่ส่งข้อยกเว้นที่ตรวจสอบแล้ว ตัวแปรนั้นจะต้องเริ่มต้นภายในบล็อกการลอง หากต้องใช้ค่านอก try block จะต้องประกาศก่อน try block ซึ่งยังไม่สามารถเริ่มต้นได้อย่างสมเหตุสมผล:
// 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()); }
สั่งนำเข้างบ
ลำดับคำสั่งการนำเข้าคือ:
- นำเข้าแอนดรอยด์
- นำเข้าจากบุคคลที่สาม (
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 สตริง |
รหัสยาว | รหัสยาว |
เนื่องจากทั้งฐานรหัส JDK และ Android ไม่สอดคล้องกันเกี่ยวกับตัวย่อ จึงแทบเป็นไปไม่ได้เลยที่จะสอดคล้องกับรหัสโดยรอบ ดังนั้น ให้ถือว่าคำย่อเป็นคำเสมอ
ใช้ความคิดเห็นสิ่งที่ต้องทำ
ใช้ข้อคิดเห็น 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
ของคุณอยู่ในรูปแบบ "At a future date do something" ตรวจสอบให้แน่ใจว่าคุณได้ระบุวันที่ที่ระบุ ("Fix by November 2005") หรือเหตุการณ์เฉพาะเจาะจง ("Remove this code after all production mixers understand protocol 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)) }