JavaRush /จาวาบล็อก /Random-TH /Java @คำอธิบายประกอบ มันคืออะไรและใช้อย่างไร?
SemperAnte
ระดับ
Донецк

Java @คำอธิบายประกอบ มันคืออะไรและใช้อย่างไร?

เผยแพร่ในกลุ่ม
บทความนี้มีไว้สำหรับผู้ที่ไม่เคยใช้งาน Annotations มาก่อน แต่ต้องการทำความเข้าใจว่ามันคืออะไรและใช้กับอะไร หากคุณมีประสบการณ์ในด้านนี้ ฉันไม่คิดว่าบทความนี้จะเพิ่มพูนความรู้ของคุณ (และอันที่จริง ฉันไม่ได้บรรลุเป้าหมายดังกล่าว) นอกจากนี้บทความนี้ไม่เหมาะสำหรับผู้ที่เพิ่งเริ่มเรียนรู้ภาษา Java หากคุณไม่เข้าใจว่าMap<>หรือHashMap<> คืออะไร หรือไม่รู้ว่า รายการ static{ }ภายในคำจำกัดความของคลาสหมายถึงอะไร หรือไม่เคยคิดทบทวนเลย ยังเร็วเกินไปที่คุณจะอ่านบทความนี้และ พยายามทำความเข้าใจว่าคำอธิบายประกอบคืออะไร เครื่องมือนี้ไม่ได้ถูกสร้างขึ้นสำหรับผู้เริ่มต้นใช้งานเนื่องจากไม่จำเป็นต้องมีความเข้าใจพื้นฐานเกี่ยวกับการโต้ตอบของคลาสและวัตถุ (ความคิดเห็นของฉัน) (ขอบคุณความคิดเห็นที่แสดงความจำเป็นในการใช้คำลงท้ายนี้) Java @คำอธิบายประกอบ  มันคืออะไรและใช้อย่างไร?  - 1มาเริ่มกันเลย คำอธิบายประกอบใน Java เป็นป้ายกำกับชนิดหนึ่งในโค้ดที่อธิบายข้อมูลเมตาสำหรับฟังก์ชัน/คลาส/แพ็คเกจ ตัวอย่างเช่น @Override Annotation ที่รู้จักกันดี ซึ่งบ่งชี้ว่าเรากำลังจะแทนที่เมธอดของคลาสพาเรนต์ ใช่ในอีกด้านหนึ่งก็เป็นไปได้หากไม่มีวิธีนี้ แต่ถ้าผู้ปกครองไม่มีวิธีนี้ก็มีความเป็นไปได้ที่จะเขียนโค้ดอย่างไร้ประโยชน์เพราะ วิธีการเฉพาะนี้อาจไม่ถูกเรียกใช้ แต่ด้วยคำอธิบายประกอบ @Override คอมไพเลอร์จะบอกเราว่า: “ฉันไม่พบวิธีการดังกล่าวในผู้ปกครอง... มีบางอย่างสกปรกที่นี่” อย่างไรก็ตาม คำอธิบายประกอบสามารถสื่อได้มากกว่าความหมายของ "เพื่อความน่าเชื่อถือ" แต่ยังเก็บข้อมูลบางอย่างที่จะใช้ในภายหลังได้

ขั้นแรก มาดูคำอธิบายประกอบที่ง่ายที่สุดที่จัดทำโดยไลบรารีมาตรฐาน

(ขอบคุณอีกครั้งสำหรับความคิดเห็น ตอนแรกฉันไม่คิดว่าจำเป็นต้องมีบล็อกนี้) ก่อนอื่น เรามาพูดคุยกันก่อนว่าคำอธิบายประกอบคืออะไร แต่ละรายการมี 2 พารามิเตอร์ หลัก ที่จำเป็น :
  • ประเภทการจัดเก็บ (การเก็บรักษา);
  • ประเภทของวัตถุที่ระบุ (เป้าหมาย)

ประเภทการจัดเก็บ

ตาม "ประเภทพื้นที่เก็บข้อมูล" เราหมายถึงขั้นตอนที่คำอธิบายประกอบของเรา "คงอยู่" ภายในชั้นเรียน หมายเหตุประกอบแต่ละรายการมี"ประเภทการเก็บรักษา" ที่เป็นไปได้เพียงหนึ่ง รายการที่ระบุไว้ในคลาส RetentionPolicy :
  • แหล่งที่มา - คำอธิบายประกอบจะใช้เฉพาะเมื่อเขียนโค้ดและคอมไพเลอร์จะละเว้น (เช่น จะไม่ถูกบันทึกหลังจากการคอมไพล์) โดยทั่วไปจะใช้สำหรับตัวประมวลผลล่วงหน้าใดๆ (ตามเงื่อนไข) หรือคำสั่งสำหรับคอมไพเลอร์
  • CLASS - คำอธิบายประกอบจะถูกเก็บรักษาไว้หลังจากการคอมไพล์ แต่ JVM จะละเว้น (เช่น ไม่สามารถใช้ขณะรันไทม์) โดยทั่วไปใช้สำหรับบริการของบริษัทอื่นที่โหลดโค้ดของคุณเป็นแอปพลิเคชันปลั๊กอิน
  • RUNTIMEเป็นคำอธิบายประกอบที่บันทึกไว้หลังจากการคอมไพล์และโหลดโดย JVM (เช่น สามารถใช้ระหว่างการทำงานของโปรแกรมได้) ใช้เป็นเครื่องหมายในโค้ดที่ส่งผลโดยตรงต่อการทำงานของโปรแกรม (ตัวอย่างจะกล่าวถึงในบทความนี้)

ประเภทของวัตถุที่ระบุไว้ข้างต้น

คำอธิบายนี้ควรใช้เกือบจะเป็นตัวอักษรเพราะ... ใน Java คำอธิบายประกอบสามารถระบุเหนืออะไรก็ได้ (ฟิลด์ คลาส ฟังก์ชัน ฯลฯ) และสำหรับคำอธิบายประกอบแต่ละรายการ จะระบุว่าสามารถระบุสิ่งใดได้บ้าง ไม่มีกฎ "สิ่งเดียว" อีกต่อไป คุณสามารถระบุคำอธิบายประกอบเหนือทุกสิ่งที่แสดงด้านล่าง หรือคุณสามารถเลือกเฉพาะองค์ประกอบที่จำเป็นของคลาสElementType :
  • ANNOTATION_TYPE - คำอธิบายประกอบอื่น
  • CONSTRUCTOR - ตัวสร้างคลาส
  • FIELD - ฟิลด์คลาส
  • LOCAL_VARIABLE - ตัวแปรท้องถิ่น
  • วิธีการ - วิธีการเรียน
  • แพคเกจ - คำอธิบายของแพ็คเกจแพ็คเกจ
  • PARAMETER - พารามิเตอร์เมธอด โมฆะสาธารณะ สวัสดี (@Annontation String param){}
  • TYPE - ระบุไว้เหนือชั้นเรียน
โดยรวมแล้ว ณ Java SE 1.8 ไลบรารีภาษามาตรฐานมีคำอธิบายประกอบถึง 10 รายการ ในบทความนี้เราจะดูสิ่งที่พบบ่อยที่สุด (ใครสนใจพวกเขาทั้งหมด ยินดีต้อนรับสู่ Javadoc ):

@แทนที่

การเก็บรักษา: แหล่งที่มา; เป้าหมาย: วิธีการ คำอธิบายประกอบนี้แสดงให้เห็นว่าวิธีการเขียนนั้นสืบทอดมาจากคลาสพาเรนต์ คำอธิบายประกอบแรกที่โปรแกรมเมอร์ Java มือใหม่ทุกคนพบเจอเมื่อใช้ IDE ที่พุช @Override เหล่านี้อย่างต่อเนื่อง บ่อยครั้งที่ครูจาก YouTube แนะนำให้ "ลบมันเพื่อไม่ให้รบกวน" หรือ "ปล่อยไว้โดยไม่สงสัยว่าทำไมมันถึงอยู่ตรงนั้น" ในความเป็นจริง คำอธิบายประกอบมีประโยชน์มากกว่า: ไม่เพียงแต่ช่วยให้คุณเข้าใจว่าวิธีการใดที่ถูกกำหนดในคลาสนี้เป็นครั้งแรก และวิธีใดที่ผู้ปกครองมีอยู่แล้ว (ซึ่งเพิ่มความสามารถในการอ่านโค้ดของคุณอย่างไม่ต้องสงสัย) แต่ยังรวมถึงคำอธิบายประกอบนี้ด้วย ทำหน้าที่เป็น "การตรวจสอบตัวเอง" ที่คุณไม่ผิดเมื่อกำหนดฟังก์ชันที่โอเวอร์โหลด

@เลิกใช้แล้ว

การเก็บรักษา: รันไทม์; เป้าหมาย: CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE คำอธิบายประกอบนี้ระบุวิธีการ คลาส หรือตัวแปรที่ "ล้าสมัย" และอาจถูกลบออกในเวอร์ชันอนาคตของผลิตภัณฑ์ โดยปกติแล้วผู้ที่อ่านเอกสารประกอบของ API ใดๆ หรือไลบรารี Java มาตรฐานเดียวกันจะพบคำอธิบายประกอบนี้ บางครั้งคำอธิบายประกอบนี้อาจถูกละเลยเนื่องจาก... มันไม่ก่อให้เกิดข้อผิดพลาดใด ๆ และโดยหลักการแล้ว ในตัวมันเองไม่ได้รบกวนชีวิตมากนัก อย่างไรก็ตาม ข้อความหลักที่คำอธิบายประกอบนี้นำเสนอคือ "เราได้คิดค้นวิธีที่สะดวกกว่าในการใช้งานฟังก์ชันนี้ ใช้มัน อย่าใช้อันเก่า" - หรืออย่างอื่น - "เราเปลี่ยนชื่อฟังก์ชัน แต่สิ่งนี้ เป็นเช่นนั้น เราทิ้งมันไว้เป็นมรดก…” (ซึ่งโดยทั่วไปแล้วก็ไม่เลวเช่นกัน) กล่าวโดยสรุป หากคุณเห็น @Deprecated จะเป็นการดีกว่าที่จะพยายามไม่ใช้สิ่งที่ค้างอยู่ เว้นแต่ว่าจำเป็นจริงๆ และอาจคุ้มค่าที่จะอ่านเอกสารประกอบอีกครั้งเพื่อทำความเข้าใจว่างานที่ทำโดยองค์ประกอบที่เลิกใช้แล้วได้รับการปฏิบัติอย่างไร ตัวอย่างเช่น แทนที่จะใช้new Date().getYear()ขอแนะนำให้ใช้Calendar.getInstance().get(Calendar.YEAR )

@SuppressWarnings

การเก็บรักษา: แหล่งที่มา; เป้าหมาย: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE คำอธิบายประกอบนี้ปิดใช้งานเอาต์พุตของคำเตือนคอมไพเลอร์ที่เกี่ยวข้องกับองค์ประกอบที่ระบุไว้ คำอธิบายประกอบ SOURCE ระบุไว้เหนือฟิลด์ วิธีการ คลาสหรือไม่

@การเก็บรักษา

การเก็บรักษา: RUNTIME; เป้าหมาย: ANNOTATION_TYPE; คำอธิบายประกอบนี้ระบุ "ประเภทการจัดเก็บ" ของคำอธิบายประกอบข้างต้นซึ่งระบุไว้ ใช่ คำอธิบายประกอบนี้ใช้เพื่อตัวมันเองด้วยซ้ำ... เวทมนตร์เท่านั้นเอง

@เป้า

การเก็บรักษา: RUNTIME; เป้าหมาย: ANNOTATION_TYPE; คำอธิบายประกอบนี้ระบุประเภทของออบเจ็กต์ที่สามารถระบุคำอธิบายประกอบที่เราสร้างได้ ใช่ และมันยังใช้สำหรับตัวคุณเองด้วย จงทำความคุ้นเคยกับมัน... ฉันคิดว่านี่คือจุดที่เราสามารถแนะนำคำอธิบายประกอบมาตรฐานของไลบรารี Java ให้สมบูรณ์ได้ เพราะ ส่วนที่เหลือไม่ค่อยได้ใช้และถึงแม้ว่าจะมีผลประโยชน์ในตัวเอง แต่ก็ไม่ใช่ทุกคนที่ต้องจัดการกับสิ่งเหล่านี้และไม่จำเป็นเลย หากคุณต้องการให้ฉันพูดเกี่ยวกับคำอธิบายประกอบเฉพาะจากไลบรารีมาตรฐาน (หรือบางทีอาจเป็นคำอธิบายประกอบเช่น @NotNull และ @Nullable ซึ่งไม่รวมอยู่ใน STL) ให้เขียนความคิดเห็น - ผู้ใช้ประเภทใดประเภทหนึ่งจะตอบคุณที่นั่น หรือ ฉันจะทำเมื่อฉันเห็นมัน หากมีคนขอคำอธิบายประกอบจำนวนมาก ฉันจะเพิ่มลงในบทความด้วย

การประยุกต์ใช้คำอธิบายประกอบ RUNTIME ในทางปฏิบัติ

จริงๆ แล้ว ฉันคิดว่านั่นเป็นการพูดคุยเชิงทฤษฎีที่เพียงพอแล้ว เรามาฝึกใช้ตัวอย่างของบอทกันดีกว่า สมมติว่าคุณต้องการเขียนบอทสำหรับเครือข่ายโซเชียลบางแห่ง เครือข่ายหลักทั้งหมด เช่น VK, Facebook, Discord มี API ของตัวเองที่ให้คุณเขียนบอทได้ สำหรับเครือข่ายเดียวกันนี้ มีไลบรารี่ที่เขียนไว้สำหรับการทำงานกับ API รวมถึงใน Java ด้วย ดังนั้นเราจะไม่เจาะลึกการทำงานของ API หรือไลบรารีใด ๆ สิ่งที่เราจำเป็นต้องรู้ในตัวอย่างนี้ก็คือบอทของเราสามารถตอบกลับข้อความที่ส่งไปยังแชทที่บอทของเราตั้งอยู่ได้ นั่นคือ สมมติว่าเรามี คลาส MessageListenerพร้อมด้วยฟังก์ชัน:
public class MessageListener
{
    public void onMessageReceived(MessageReceivedEvent event)
    {
    }
}
มีหน้าที่ประมวลผลข้อความที่ได้รับ สิ่งที่เราต้องการจาก คลาส MessageReceivedEventคือสตริงของข้อความที่ได้รับ (เช่น “Hello” หรือ “Bot, hello”) ควรพิจารณา: ในห้องสมุดต่าง ๆ คลาสเหล่านี้ถูกเรียกต่างกัน ฉันใช้ไลบรารี่สำหรับ Discord ดังนั้นเราจึงต้องการให้บอทตอบสนองต่อคำสั่งบางคำสั่งที่ขึ้นต้นด้วย “บอท” (ไม่ว่าจะมีหรือไม่มีลูกน้ำ - ตัดสินใจด้วยตัวเอง: เพื่อประโยชน์ของบทเรียนนี้ เราจะถือว่าไม่ควรมีลูกน้ำอยู่ที่นั่น) นั่นคือฟังก์ชันของเราจะเริ่มต้นด้วยสิ่งที่ชอบ:
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {

    }
}
และตอนนี้เรามีตัวเลือกมากมายสำหรับการใช้คำสั่งนี้หรือคำสั่งนั้น ไม่ต้องสงสัยเลยว่าก่อนอื่นคุณต้องแยกคำสั่งออกจากอาร์กิวเมนต์นั่นคือแยกออกเป็นอาร์เรย์
public void onMessageReceived(MessageReceivedEvent event)
{
    //Убираем чувствительность к регистру (БоТ, бОт и т.д.)
    String message = event.getMessage().toLowerCase();
    if (message.startsWith("бот"))
    {
        try
        {
            //получим массив {"Бот", "(команду)", "аргумент1", "аргумент2",... "аргументN"};
            String[] args = message.split(" ");
            //Для удобства уберем "бот" и отделим команду от аргументов
            String command = args[1];
            String[] nArgs = Arrays.copyOfRange(args, 2, args.length);
            //Получor command = "(команда)"; nArgs = {"аргумент1", "аргумент2",..."аргументN"};
            //Данный массив может быть пустым
        }
        catch (ArrayIndexOutOfBoundsException e)
        {
            //Вывод списка команд or Howого-либо messages
            //В случае если просто написать "Бот"
        }
    }
}
ไม่มีทางที่เราจะหลีกเลี่ยงโค้ดชิ้นนี้ได้ เนื่องจากการแยกคำสั่งออกจากอาร์กิวเมนต์เป็นสิ่งจำเป็นเสมอ แต่แล้วเราก็มีทางเลือก:
  • ทำ if(command.equalsIngnoreCase("..."))
  • ทำสวิตช์ (คำสั่ง)
  • ดำเนินการด้วยวิธีอื่น...
  • หรือหันไปใช้คำอธิบายประกอบ
และในที่สุดเราก็มาถึงส่วนที่ใช้งานได้จริงของการใช้คำอธิบายประกอบแล้ว ลองดูโค้ดคำอธิบายประกอบสำหรับงานของเรา (แน่นอนว่าอาจแตกต่างกันไป)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//Указывает, что наша Аннотация может быть использована
//Во время выполнения через Reflection (нам How раз это нужно).
@Retention(RetentionPolicy.RUNTIME)

//Указывает, что целью нашей Аннотации является метод
//Не класс, не переменная, не поле, а именно метод.
@Target(ElementType.METHOD)
public @interface Command //Описание. Заметим, что перед interface стоит @;
{
    //Команда за которую будет отвечать функция (например "привет");
    String name();

     //Аргументы команды, использоваться будут для вывода списка команд
    String args();

     //Минимальное количество аргументов, сразу присвоor 0 (логично)
    int minArgs() default 0;

    //Описание, тоже для списка
    String desc();

     //Максимальное число аргументов. В целом не обязательно, но тоже можно использовать
    int maxArgs() default Integer.MAX_VALUE;

     //Показывать ли команду в списке (вовсе необязательная строка, но мало ли, пригодится!)
    boolean showInHelp() default true;

    //Какие команды будут считаться эквивалентными нашей
    //(Например для "привет", это может быть "Здаров", "Прив" и т.д.)
    //Под каждый случай заводить функцию - не рационально
    String[] aliases();

}
สำคัญ! พารามิเตอร์แต่ละรายการจะอธิบายว่าเป็นฟังก์ชัน (มีวงเล็บ) เฉพาะค่าพื้นฐาน, String , Enum เท่านั้น ที่สามารถใช้เป็นพารามิเตอร์ได้ คุณไม่สามารถเขียนList<String> args(); - ข้อผิดพลาด. ตอนนี้เราได้อธิบาย Annotation แล้ว มาสร้างคลาสกัน เรียกมันว่า CommandListener
public class CommandListener
{
    @Command(name = "привет",
            args = "",
            desc = "Будь культурным, поздоровайся",
            showInHelp = false,
            aliases = {"здаров"})
    public void hello(String[] args)
    {
        //Какой-то функционал, на Ваше усмотрение.
    }

    @Command(name = "пока",
            args = "",
            desc = "",
            aliases = {"удачи"})
    public void bye(String[] args)
    {
         // Функционал
    }

    @Command(name = "помощь",
            args = "",
            desc = "Выводит список команд",
            aliases = {"help", "команды"})
    public void help(String[] args)
    {
        StringBuilder sb = new StringBuilder("Список команд: \n");
        for (Method m : this.getClass().getDeclaredMethods())
        {
            if (m.isAnnotationPresent(Command.class))
            {
                Command com = m.getAnnotation(Command.class);
                if (com.showInHelp()) //Если нужно показывать команду в списке.
                {
                    sb.append("Бот, ")
                       .append(com.name()).append(" ")
                       .append(com.args()).append(" - ")
                       .append(com.desc()).append("\n");
                }
            }
        }
        //Отправка sb.toString();

    }
}
เป็นที่น่าสังเกตว่าความไม่สะดวกเล็กน้อยประการหนึ่ง: t.c. ขณะนี้เรากำลังต่อสู้เพื่อความเป็นสากล ฟังก์ชันทั้งหมดจะต้องมีรายการพารามิเตอร์ที่เป็นทางการเหมือนกัน ดังนั้นแม้ว่าคำสั่งจะไม่มีอาร์กิวเมนต์ ฟังก์ชันนั้นจะต้องมีพารามิเตอร์String[] args ตอนนี้เราได้อธิบาย 3 คำสั่งแล้ว: สวัสดี บาย ช่วยเหลือ ตอนนี้เรามาแก้ไขMessageListener ของเรา เพื่อทำสิ่งนี้ เพื่อความสะดวกและรวดเร็วในการทำงาน เราจะจัดเก็บคำสั่งของเราไว้ในHashMap ทันที :
public class MessageListner
{
    //Map который хранит How ключ команду
    //А How meaning функцию которая будет обрабатывать команду
    private static final Map<String, Method> COMMANDS = new HashMap<>();

    //Объект класса с командами (по сути нужен нам для рефлексии)
    private static final CommandListener LISTENER = new CommandListener();

    static
    {
       //Берем список всех методов в классе CommandListener
        for (Method m : LISTENER.getClass().getDeclaredMethods())
        {
            //Смотрим, есть ли у метода нужная нам Аннотация @Command
            if (m.isAnnotationPresent(Command.class))
            {
                //Берем an object нашей Аннотации
                Command cmd = m.getAnnotation(Command.class);
                //Кладем в качестве ключа нашей карты параметр name()
                //Определенный у нашей аннотации,
                //m — переменная, хранящая наш метод
                COMMANDS.put(cmd.name(), m);

                //Также заносим каждый элемент aliases
               //Как ключ указывающий на тот же самый метод.
                for (String s : cmd.aliases())
                {
                    COMMANDS.put(s, m);
                }
            }
        }
    }

    public void onMessageReceived(MessageReceivedEvent event)
    {

        String message = event.getMessage().toLowerCase();
        if (message.startsWith("бот"))
        {
            try
            {
                String[] args = message.split(" ");
                String command = args[1];
                String[] nArgs = Arrays.copyOfRange(args, 2, args.length);
                Method m = COMMANDS.get(command);
                if (m == null)
                {
                    //(вывод помощи)
                    return;
                }
                Command com = m.getAnnotation(Command.class);
                if (nArgs.length < com.minArgs())
                {
                    //что-то если аргументов меньше чем нужно
                }
                else if (nArgs.length > com.maxArgs())
                {
                    //что-то если аргументов больше чем нужно
                }
                //Через рефлексию вызываем нашу функцию-обработчик
                //Именно потому что мы всегда передаем nArgs у функции должен быть параметр
                //String[] args — иначе она просто не будет найдена;
                m.invoke(LISTENER, nArgs);
            }
            catch (ArrayIndexOutOfBoundsException e)
            {
                //Вывод списка команд or Howого-либо messages
                //В случае если просто написать "Бот"
            }
        }
    }
}
นั่นคือทั้งหมดที่จำเป็นสำหรับทีมของเราในการทำงาน ตอนนี้การเพิ่มคำสั่งใหม่ไม่ใช่กรณีใหม่ ซึ่งจะต้องคำนวณจำนวนอาร์กิวเมนต์ใหม่ และความช่วยเหลือจะต้องถูกเขียนใหม่ด้วย โดยเพิ่มบรรทัดใหม่ลงไป ตอนนี้ ในการเพิ่มคำสั่ง เราเพียงแค่ต้องเพิ่มฟังก์ชันใหม่ด้วยคำอธิบายประกอบ @Command ในคลาส CommandListener เพียงเท่านี้ - เพิ่มคำสั่งแล้ว คำนึงถึงกรณีและปัญหาต่างๆ เพิ่มความช่วยเหลือจะถูกเพิ่มโดยอัตโนมัติ ไม่ต้องสงสัยเลยว่าปัญหานี้สามารถแก้ไขได้ด้วยวิธีอื่นมากมาย ใช่ ทุกอย่างที่สามารถทำได้ด้วยความช่วยเหลือของคำอธิบายประกอบ/การสะท้อนกลับสามารถทำได้โดยไม่ต้องใช้สิ่งเหล่านั้น คำถามเดียวคือความสะดวก การเพิ่มประสิทธิภาพ และขนาดโค้ด แน่นอนว่าการติดคำอธิบายประกอบในทุกที่ที่มีคำใบ้เพียงเล็กน้อยว่าจะสามารถใช้งานได้ มันไม่ใช่ตัวเลือกที่สมเหตุสมผลที่สุด ในทุกสิ่งที่คุณจำเป็นต้องรู้ว่าเมื่อใดควรหยุด =) แต่เมื่อเขียน API, ไลบรารี หรือโปรแกรมที่สามารถทำซ้ำโค้ดประเภทเดียวกัน (แต่ไม่เหมือนกันทุกประการ) คำอธิบายประกอบถือเป็นโซลูชันที่ดีที่สุดอย่างไม่ต้องสงสัย
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION