ใช้ไลบรารีของบุคคลที่สาม - ใช้ wrapper เสมอหรือไม่


78

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

ตัวอย่างโครงการ PHP ส่วนใหญ่ของเราโดยตรงใช้ log4php เป็นเฟรมเวิร์กการบันทึกนั่นคือยกตัวอย่างผ่าน \ Logger :: getLogger () พวกมันใช้ -> info () หรือ -> warn () วิธีอื่น ๆ ในอนาคต อย่างไรก็ตามกรอบการบันทึกข้อมูลสมมุติฐานอาจปรากฏขึ้นซึ่งจะดีกว่าในบางวิธี โครงการทั้งหมดที่เข้าคู่กับลายเซ็นเมธอด log4php จะต้องเปลี่ยนแปลงในหลาย ๆ แห่งเพื่อให้เหมาะสมกับลายเซ็นใหม่ สิ่งนี้จะส่งผลกระทบอย่างกว้างขวางต่อ codebase และการเปลี่ยนแปลงใด ๆ อาจเป็นปัญหาได้

ในการพิสูจน์โค้ดใหม่ในอนาคตจากสถานการณ์ประเภทนี้ฉันมักจะพิจารณา (และบางครั้งใช้) คลาส wrapper เพื่อห่อหุ้มฟังก์ชั่นการบันทึกและทำให้ง่ายขึ้นแม้ว่าจะไม่เข้าใจผิดเพื่อเปลี่ยนวิธีการบันทึกในอนาคตโดยมีการเปลี่ยนแปลงน้อยที่สุด ; รหัสเรียกเสื้อคลุม, เสื้อคลุมผ่านการเรียกร้องให้กรอบการเข้าสู่ระบบแบบหวือหวา

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

แก้ไข: ข้อควรพิจารณาเพิ่มเติม - การใช้การฉีดพึ่งพาและการทดสอบสองเท่าต้องการให้เราสรุป APIs ส่วนใหญ่อยู่แล้ว ("ฉันต้องการตรวจสอบรหัสของฉันดำเนินการและอัปเดตสถานะ แต่ไม่เขียนความคิดเห็นบันทึก / เข้าถึงฐานข้อมูลจริง") นี่ไม่ใช่ decider ใช่ไหม


3
log4XYZ เป็นเครื่องหมายการค้าที่แข็งแกร่งเช่นนี้ API ของมันจะเปลี่ยนเร็วกว่าเมื่อ API สำหรับรายการที่เชื่อมโยงจะมี ทั้งคู่เป็นปัญหาที่แก้กันมานานแล้ว
งาน

1
สำเนาซ้ำกันของคำถาม SO นี้: stackoverflow.com/questions/1916030/…
Michael Borgwardt

1
หากคุณเพิ่งใช้มันภายในไม่ว่าคุณจะห่อหรือไม่ก็เป็นเพียงการแลกเปลี่ยนระหว่างงานที่รู้จักตอนนี้และงานที่เป็นไปได้ในภายหลัง การเรียกการตัดสิน แต่สิ่งที่ผู้ตอบคำถามอื่น ๆ ดูเหมือนจะละเลยที่จะพูดคุยคือไม่ว่าจะเป็นการพึ่งพาAPIหรือการพึ่งพาการใช้งาน กล่าวอีกนัยหนึ่งคุณรั่วคลาสจาก API ของบุคคลที่สามผ่าน API สาธารณะของคุณและเปิดเผยให้ผู้ใช้เห็นหรือไม่ ในกรณีนี้มันไม่ใช่เรื่องง่ายอีกต่อไปที่จะย้ายไปยังไลบรารีอื่นปัญหาที่ยากคือตอนนี้มันเป็นไปไม่ได้โดยไม่ทำให้ API ของคุณแตก มันแย่มาก!
อีเลียส Vasylenko

1
สำหรับการอ้างอิงเพิ่มเติม: รูปแบบนี้เรียกว่าonion-architectureซึ่งโครงสร้างพื้นฐานภายนอก (คุณเรียกว่า lib ภายนอก) ถูกซ่อนอยู่หลังส่วนต่อประสาน
k3b

คำตอบ:


42

หากคุณใช้ชุดย่อยเล็ก ๆ ของ API ของบุคคลที่สามเท่านั้นคุณควรเขียน wrapper ซึ่งจะช่วยในการห่อหุ้มและซ่อนข้อมูลเพื่อให้แน่ใจว่าคุณจะไม่เปิดเผย API ขนาดใหญ่ในรหัสของคุณ นอกจากนี้ยังสามารถช่วยในการตรวจสอบให้แน่ใจว่าการทำงานใด ๆ ที่คุณไม่ต้องการใช้คือ "ซ่อน"

อีกเหตุผลที่ดีสำหรับเสื้อคลุมคือถ้าคุณคาดว่าจะเปลี่ยนห้องสมุดบุคคลที่สาม หากนี่เป็นโครงสร้างพื้นฐานที่คุณรู้ว่าคุณจะไม่เปลี่ยนแปลงอย่าเขียน wrapper ของมัน


จุดที่ดี แต่เราได้รับการสอนว่าโค้ดที่มีการเชื่อมโยงอย่างใกล้ชิดนั้นไม่ดีด้วยเหตุผลที่เข้าใจกันหลายประการ (ยากต่อการทดสอบยากต่อการปรับโครงสร้าง ฯลฯ ) ข้อความทางเลือกของคำถามคือ "ถ้าคัปปลิ้งไม่ดีทำไมมันจึงโอเคกับ API?"
ล็อตออฟเซ็ต

7
@lotsoffreetime คุณไม่สามารถหลีกเลี่ยงการเชื่อมต่อกับ API ดังนั้นจะเป็นการดีกว่าถ้าคุณใช้ API ของคุณ ด้วยวิธีนี้คุณสามารถเปลี่ยนไลบรารี่และโดยทั่วไปไม่จำเป็นต้องเปลี่ยน API ที่ให้มาโดย wrapper
George Marian

@ george-marian หากฉันไม่สามารถหลีกเลี่ยงการใช้ API ที่ระบุได้ฉันสามารถลดจุดสัมผัสได้อย่างแน่นอน คำถามคือฉันควรจะพยายามทำสิ่งนี้ตลอดเวลาหรือว่าทำสิ่งนั้นมากเกินไป?
ล็อตออฟเซ็ต

2
@lotsoffreetime นั่นเป็นคำถามที่ตอบยาก ฉันขยายคำตอบของฉันจนจบ (โดยทั่วไปจะลดลงไปเป็นจำนวนมาก ifs.)
จอร์จแมเรียน

2
@lotsoffreetime: หากคุณมีเวลาว่างมากมายคุณสามารถทำได้ แต่ฉันขอแนะนำให้เขียน API wrapper ยกเว้นภายใต้เงื่อนไขนี้: 1) API ดั้งเดิมอยู่ในระดับต่ำมากดังนั้นคุณจึงเขียน API ระดับสูงขึ้นเพื่อให้เหมาะกับโครงการเฉพาะของคุณต้องการดีกว่าหรือ 2) คุณมีแผนใน ในอนาคตอันใกล้เพื่อเปลี่ยนห้องสมุดคุณกำลังใช้ห้องสมุดปัจจุบันเป็นเพียงก้าวย่างในขณะที่ค้นหาสิ่งที่ดีกว่า
Lie Ryan

28

โดยไม่ทราบว่ามีฟีเจอร์ใหม่ที่ยอดเยี่ยมอะไรที่ผู้บันทึกที่ได้รับการปรับปรุงในอนาคตที่ถูกกล่าวหานี้จะมีวิธีใดคุณจะเขียน wrapper เหตุผลที่เลือกมากที่สุดคือการมีเสื้อคลุมของคุณยกตัวอย่างการเรียงลำดับของชั้นคนตัดไม้บางส่วนและมีวิธีการเหมือนหรือ->info() ->warn()กล่าวอีกนัยหนึ่งก็เหมือนกับ API ปัจจุบันของคุณ

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

อีกวิธีหนึ่งการห่อตัวจะใช้งานได้ก็ต่อเมื่อคุณทราบ API ทั้งหมดที่คุณต้องการห่อแล้ว ตัวอย่างที่ดีคือหากแอปพลิเคชันของคุณต้องการสนับสนุนไดรเวอร์ฐานข้อมูลระบบปฏิบัติการหรือเวอร์ชัน PHP ที่แตกต่างกัน


"... wrappers นั้นสมเหตุสมผลเมื่อคุณรู้จัก API ทั้งหมดที่คุณต้องการห่อเท่านั้น" นี่จะเป็นจริงถ้าฉันจับคู่ API ใน wrapper; บางทีฉันควรจะใช้คำว่า "ห่อหุ้ม" อย่างยิ่งกว่ากระดาษห่อ ฉันจะสรุปการเรียก API เหล่านี้เพื่อ "บันทึกข้อความนี้อย่างใด" มากกว่า "call foo :: log () ด้วยพารามิเตอร์นี้"
ล็อตออฟเซ็ต

"โดยไม่ทราบว่ามีฟีเจอร์ใหม่ที่ยอดเยี่ยมอะไรที่ผู้บันทึกที่ได้รับการปรับปรุงในอนาคตที่ถูกกล่าวหาว่าจะมีในนี้ @ kevin-cline ด้านล่างพูดถึงคนตัดไม้ในอนาคตที่มีประสิทธิภาพที่ดีกว่าไม่ใช่คุณลักษณะที่ใหม่กว่า ในกรณีนี้ไม่มี API ใหม่ที่จะรวมเป็นเพียงวิธีการจากโรงงานอื่น
ล็อตออฟไทม์

27

โดยการห่อห้องสมุดบุคคลที่สามให้คุณเพิ่มเลเยอร์สิ่งที่เป็นนามธรรมเพิ่มเติมที่ด้านบนของมัน นี่มีข้อดีบางประการ:

  • ฐานรหัสของคุณมีความยืดหยุ่นมากขึ้นต่อการเปลี่ยนแปลง

    ถ้าคุณเคยต้องแทนที่ห้องสมุดกับอีกหนึ่งคนที่คุณจะต้องเปลี่ยนการดำเนินงานของคุณในเสื้อคลุมของคุณ - ในสถานที่หนึ่ง คุณสามารถเปลี่ยนการใช้งานของ wrapper และไม่ต้องเปลี่ยนอะไรเกี่ยวกับสิ่งอื่นในคำอื่น ๆ ที่คุณมีระบบคู่ที่หลวม มิฉะนั้นคุณจะต้องผ่าน codebase ทั้งหมดของคุณและทำการแก้ไขทุกที่ - ซึ่งเห็นได้ชัดว่าไม่ใช่สิ่งที่คุณต้องการ

  • คุณสามารถกำหนด API ของ wrapper ได้อย่างอิสระจาก API ของไลบรารี

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

  • การทดสอบหน่วยเป็นวิธีที่ง่ายกว่า

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

  • คุณสร้างระบบคู่ที่หลวม

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


ตัวอย่างการปฏิบัติ

มาซื่อสัตย์กันเถอะ ผู้คนสามารถโต้เถียงเกี่ยวกับข้อดีและข้อเสียของบางสิ่งเช่นนี้มาหลายชั่วโมง - ซึ่งเป็นเหตุผลว่าทำไมฉันถึงค่อนข้างแสดงตัวอย่างให้คุณเห็น

สมมติว่าคุณมีแอพ Android บางประเภทและคุณต้องดาวน์โหลดรูปภาพ มีพวงของห้องสมุดออกมีที่ให้โหลดและแคชภาพสายลมเช่นเป็นปิกัสโซหรือโหลดภาพสากล

ตอนนี้เราสามารถกำหนดอินเทอร์เฟซที่เราจะใช้เพื่อตัดไลบรารีใดก็ตามที่เราใช้:

public interface ImageService {
    Bitmap load(String url);
}

นี่คืออินเทอร์เฟซที่ตอนนี้เราสามารถใช้งานได้ตลอดทั้งแอพเมื่อใดก็ตามที่เราต้องการโหลดภาพ ImageServiceเราสามารถสร้างการดำเนินงานของอินเตอร์เฟซนี้และใช้ฉีดพึ่งพาการฉีดตัวอย่างของการดำเนินงานที่ทุกที่ที่เราใช้

สมมติว่าเราเริ่มใช้ Picasso เป็นครั้งแรก ตอนนี้เราสามารถเขียนการใช้งานImageServiceที่ใช้ Picasso ภายใน:

public class PicassoImageService implements ImageService {

    private final Context mContext;

    public PicassoImageService(Context context) {
        mContext = context;
    }

    @Override
    public Bitmap load(String url) {
        return Picasso.with(mContext).load(url).get();
    }
}

ตรงไปตรงมาถ้าคุณถามฉัน กระดาษห่อหุ้มรอบ ๆ ห้องสมุดไม่จำเป็นต้องมีความซับซ้อนที่จะเป็นประโยชน์ อินเทอร์เฟซและการใช้งานนั้นมีโค้ดที่รวมกันน้อยกว่า 25 บรรทัดดังนั้นมันจึงแทบไม่มีความพยายามในการสร้างสิ่งนี้ แต่เราได้รับบางสิ่งจากการทำเช่นนี้ ดูContextฟิลด์ในการใช้งานหรือไม่ เฟรมเวิร์กการพึ่งพาของตัวเลือกที่คุณเลือกจะดูแลการพึ่งพานั้นก่อนที่เราจะใช้ImageServiceแอพของคุณตอนนี้ไม่จำเป็นต้องสนใจว่ารูปภาพจะถูกดาวน์โหลดอย่างไรและอะไรก็ตามที่ห้องสมุดมี แอปทั้งหมดของคุณเห็นคือImageServiceและเมื่อจำเป็นต้องมีภาพที่มันเรียกload()พร้อมกับ url - ง่ายและตรงไปตรงมา

อย่างไรก็ตามประโยชน์ที่แท้จริงมาเมื่อเราเริ่มเปลี่ยนสิ่งต่าง ๆ ลองนึกภาพตอนนี้เราจำเป็นต้องแทนที่ Picasso ด้วย Universal Image Loader เพราะ Picasso ไม่รองรับคุณสมบัติบางอย่างที่เราต้องการในตอนนี้ ตอนนี้เราต้องใช้โค๊ดโค้ดโค๊ดของเราและแทนที่การเรียกไปยังปิกัสโซ่อย่างน่าเบื่อและจากนั้นจัดการกับข้อผิดพลาดในการคอมไพล์หลายครั้งเพราะเราลืมโทรปิกัสโซ่ไปสองสามครั้ง? ไม่ทั้งหมดที่เราต้องทำคือสร้างการใช้งานใหม่ImageServiceและบอกกรอบการฉีดของเราเพื่อใช้งานการดำเนินการนี้จากนี้:

public class UniversalImageLoaderImageService implements ImageService {

    private final ImageLoader mImageLoader;

    public UniversalImageLoaderImageService(Context context) {

        DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
                .cacheInMemory(true)
                .cacheOnDisk(true)
                .build();

        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
                .defaultDisplayImageOptions(defaultOptions)
                .build();

        mImageLoader = ImageLoader.getInstance();
        mImageLoader.init(config);
    }

    @Override
    public Bitmap load(String url) {
        return mImageLoader.loadImageSync(url);
    }
}

ในขณะที่คุณสามารถเห็นการใช้งานอาจแตกต่างกันมาก แต่ก็ไม่สำคัญ เราไม่จำเป็นต้องเปลี่ยนรหัสบรรทัดเดียวที่ใดก็ได้ในแอปของเรา เราใช้ห้องสมุดที่แตกต่างอย่างสิ้นเชิงซึ่งอาจมีคุณสมบัติแตกต่างไปจากเดิมอย่างสิ้นเชิงหรืออาจมีการใช้แตกต่างกันมาก แต่แอพของเราไม่สนใจ เช่นเดียวกับที่ผ่านมาแอพที่เหลือของเราเพิ่งเห็นImageServiceอินเทอร์เฟซด้วยload()วิธีการและวิธีการใช้งานนี้ไม่สำคัญอีกต่อไป

อย่างน้อยสำหรับฉันทั้งหมดนี้แล้วเสียงสวยดีอยู่แล้ว แต่รอ! ยังมีอีกมาก ImageServiceลองนึกภาพคุณกำลังเขียนการทดสอบหน่วยสำหรับการเรียนที่คุณกำลังทำงานบนและชั้นนี้ใช้ แน่นอนว่าคุณไม่สามารถปล่อยให้การทดสอบหน่วยของคุณทำการเรียกเครือข่ายไปยังทรัพยากรบางอย่างที่ตั้งอยู่บนเซิร์ฟเวอร์อื่น แต่เนื่องจากคุณกำลังใช้อยู่ImageServiceคุณสามารถปล่อยload()คืนค่าสแตติกที่Bitmapใช้สำหรับการทดสอบหน่วยโดยการใช้การเยาะเย้ยImageService:

public class MockImageService implements ImageService {

    private final Bitmap mMockBitmap;

    public MockImageService(Bitmap mockBitmap) {
        mMockBitmap = mockBitmap;
    }

    @Override
    public Bitmap load(String url) {
        return mMockBitmap;
    }
}

ในการสรุปโดยการห่อไลบรารี่ของบุคคลที่สามฐานรหัสของคุณจะมีความยืดหยุ่นมากขึ้นต่อการเปลี่ยนแปลงโดยรวมง่ายขึ้นง่ายต่อการทดสอบและคุณลดการเชื่อมต่อของส่วนประกอบต่าง ๆ ในซอฟต์แวร์ของคุณ - สิ่งต่าง ๆ ที่สำคัญยิ่งขึ้นเรื่อย ๆ


1
สิ่งนี้ใช้ได้กับ API ที่ไม่เสถียรเช่นกัน รหัสของเราไม่เปลี่ยนแปลงใน 1,000 แห่งเพราะห้องสมุดต้นแบบเปลี่ยนไป คำตอบที่ดีมาก
RubberDuck

คำตอบที่กระชับและชัดเจนมาก ฉันทำงานส่วนหน้าบนเว็บ จำนวนของการเปลี่ยนแปลงในแนวนอนนั้นเป็นบ้า ความจริงที่ว่าคน "คิด" จะไม่มีการเปลี่ยนแปลงไม่ได้หมายความว่าจะไม่มีการเปลี่ยนแปลงใด ๆ ฉันเห็นกล่าวถึง YAGNI ฉันต้องการที่จะเพิ่มตัวย่อใหม่ YDKYAGNI คุณไม่ทราบว่าคุณไม่ต้องการมัน โดยเฉพาะอย่างยิ่งกับการใช้งานที่เกี่ยวข้องกับเว็บ ตามกฎแล้วฉันมักจะตัดไลบรารีที่แสดง API ขนาดเล็กเท่านั้น (เช่น select2) ไลบรารีที่มีขนาดใหญ่ขึ้นจะส่งผลต่อสถาปัตยกรรมของคุณและหมายถึงการห่อหุ้มหมายความว่าคุณคาดหวังว่าสถาปัตยกรรมของคุณจะเปลี่ยนแปลง แต่อาจมีความเป็นไปได้น้อยกว่า
Byebye

คำตอบของคุณมีประโยชน์มากและนำเสนอแนวคิดพร้อมตัวอย่างทำให้แนวคิดนั้นชัดเจนยิ่งขึ้น
Anil Gorthy

24

ฉันคิดว่าการห่อห้องสมุดบุคคลที่สามในวันนี้ในกรณีที่สิ่งที่ดีกว่ามาในวันพรุ่งนี้เป็นการละเมิด YAGNI ที่สิ้นเปลืองมาก หากคุณกำลังเรียกรหัสของบุคคลที่สามซ้ำ ๆ ในลักษณะที่แปลกไปที่แอปพลิเคชันของคุณคุณจะ (ควร) ปรับโครงสร้างการเรียกเหล่านั้นในคลาสการตัดเพื่อกำจัดการทำซ้ำ มิฉะนั้นคุณจะใช้ไลบรารี API อย่างสมบูรณ์และ wrapper ใด ๆ ก็จะดูเหมือนกับไลบรารีเอง

ตอนนี้สมมติว่าไลบรารีใหม่ปรากฏขึ้นพร้อมกับประสิทธิภาพที่เหนือกว่าหรืออะไรก็ตาม ในกรณีแรกคุณเพิ่งเขียน wrapper สำหรับ API ใหม่ ไม่มีปัญหา.

ในกรณีที่สองคุณสร้าง wrapper ที่ปรับอินเทอร์เฟซเก่าเพื่อขับไลบรารี่ใหม่ ทำงานเพิ่มอีกเล็กน้อย แต่ไม่มีปัญหาและไม่ต้องทำงานมากไปกว่านี้หากคุณได้เขียนกระดาษห่อไว้ก่อนหน้านี้


4
ฉันไม่คิดว่าจำเป็นต้องใช้ YAGNI ในสถานการณ์นี้ มันไม่ได้เกี่ยวกับการสร้างฟังก์ชั่นในกรณีที่คุณอาจต้องการมันในอนาคต มันเกี่ยวกับการสร้างความยืดหยุ่นให้กับสถาปัตยกรรม หากไม่จำเป็นต้องใช้ความยืดหยุ่นดังกล่าวใช่ YAGNI อย่างไรก็ตามการตัดสินใจนั้นมีแนวโน้มที่จะเกิดขึ้นในอนาคตเมื่อการเปลี่ยนแปลงมีแนวโน้มที่จะเจ็บปวด
George Marian

7
@ George Marian: ปัญหาคือ 95% ของเวลาคุณจะไม่ต้องการความยืดหยุ่นในการเปลี่ยนแปลง หากคุณต้องการเปลี่ยนไปใช้ห้องสมุดใหม่ในอนาคตที่มีประสิทธิภาพที่เหนือกว่าควรค้นหา / เปลี่ยนสายหรือเขียนคำพูดคลุมเครือเมื่อคุณต้องการ ในทางกลับกันถ้าไลบรารีใหม่ของคุณมาพร้อมกับฟังก์ชันการทำงานที่แตกต่างกันตอนนี้ wrapper จะกลายเป็นสิ่งกีดขวางเนื่องจากตอนนี้คุณมีปัญหาสองประการคือการย้ายรหัสเก่าเพื่อใช้ประโยชน์จากคุณสมบัติใหม่และการบำรุงรักษา wrapper
Lie Ryan

3
@lotsoffreetime: จุดประสงค์ของ "การออกแบบที่ดี" คือการลดต้นทุนโดยรวมของแอปพลิเคชันตลอดอายุการใช้งาน การเพิ่มเลเยอร์ทางอ้อมสำหรับการเปลี่ยนแปลงในอนาคตที่เกิดขึ้นนั้นเป็นประกันราคาแพงมาก ฉันไม่เคยเห็นใครตระหนักถึงการออมใด ๆ จากวิธีการที่ มันเพิ่งสร้างงานที่ไม่สำคัญสำหรับโปรแกรมเมอร์ซึ่งเวลาจะถูกใช้ไปกับความต้องการเฉพาะของลูกค้ามากขึ้น ส่วนใหญ่หากคุณกำลังเขียนรหัสที่ไม่เฉพาะเจาะจงกับลูกค้าของคุณคุณกำลังเสียเวลาและเงิน
kevin cline

1
@ George: หากการเปลี่ยนแปลงเหล่านี้เจ็บปวดฉันคิดว่านั่นเป็นกลิ่นของกระบวนการ ใน Java ฉันจะสร้างคลาสใหม่ที่มีชื่อเดียวกันกับคลาสเก่า แต่ในแพ็คเกจอื่นให้เปลี่ยนชื่อแพคเกจเก่าที่เกิดขึ้นทั้งหมดและรันการทดสอบอัตโนมัติอีกครั้ง
วินไคลน์

1
@ เควินนั่นเป็นงานที่มากกว่าและมีความเสี่ยงมากกว่าเพียงแค่อัปเดตเสื้อคลุมและทำการทดสอบ
George Marian

9

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

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

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

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


ในกรณีของการทดสอบหน่วยที่สามารถใช้การเยาะเย้ยของ API เพื่อลดหน่วยในการทดสอบให้น้อยที่สุด "การเปลี่ยนศักยภาพ" ไม่ใช่ปัจจัย ต้องบอกว่านี่เป็นคำตอบที่ฉันโปรดปรานเพราะใกล้เคียงกับที่ฉันคิด ลุงบ๊อบจะพูดอะไร :)
lotsoffreetime

นอกจากนี้โครงการขนาดเล็ก (ไม่มีทีมข้อมูลจำเพาะพื้นฐาน ฯลฯ ) มีกฎของตนเองที่คุณสามารถละเมิดแนวปฏิบัติที่ดีเช่นนี้และออกไปกับมันในระดับหนึ่ง แต่นั่นเป็นคำถามที่แตกต่างกัน ...
lotsoffreetime

1

นอกจากสิ่งที่@Odedพูดแล้วฉันต้องการเพิ่มคำตอบนี้เพื่อวัตถุประสงค์พิเศษในการเข้าสู่ระบบ


ฉันมีอินเทอร์เฟซสำหรับการบันทึกเสมอ แต่ฉันไม่เคยต้องแทนที่log4fooเฟรมเวิร์ก

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

มันเป็นกรณีพิเศษของ YAGNI แม้ว่าฉันไม่ต้องการมันใช้เวลาไม่นานและฉันก็รู้สึกปลอดภัยกว่า หากวันที่มีการแลกเปลี่ยนคนตัดไม้มาจริง ๆ ฉันจะดีใจที่ฉันลงทุนครึ่งชั่วโมงเพราะมันจะช่วยฉันได้มากกว่าหนึ่งวันในการแลกเปลี่ยนสายในโครงการโลกแห่งความจริง และฉันไม่เคยเขียนหรือเห็นการทดสอบหน่วยสำหรับการบันทึก (นอกเหนือจากการทดสอบสำหรับการใช้งานตัวบันทึก) ดังนั้นคาดว่าจะเกิดข้อบกพร่องโดยไม่ต้องใช้เครื่องมือห่อหุ้ม


ฉันไม่คิดว่าจะเปลี่ยน log4foo แต่มันเป็นที่รู้จักอย่างกว้างขวางและทำหน้าที่เป็นตัวอย่าง นอกจากนี้ยังเป็นที่น่าสนใจว่าคำตอบทั้งสองนั้นมาจากไหน - "ไม่ห่อ" "ห่อในกรณี"
ล็อตออฟเซ็ต

@ เหยี่ยว: คุณห่อทุกอย่าง? ORM, ส่วนต่อประสาน, ชั้นเรียนภาษาหลัก? ท้ายที่สุดไม่มีใครสามารถบอกได้ว่าเมื่อใดที่ HashMap ที่ดีกว่าอาจต้องการ
วินไคลน์

1

ฉันกำลังจัดการกับปัญหาที่แน่นอนนี้ในโครงการที่ฉันกำลังทำงานอยู่ แต่ในกรณีของฉันไลบรารีสำหรับกราฟิกและดังนั้นฉันสามารถ จำกัด มันใช้กับคลาสจำนวนเล็กน้อยที่จัดการกับกราฟิกเมื่อเทียบกับการโรยมันตลอดทั้งโครงการ ดังนั้นมันจึงค่อนข้างง่ายที่จะเปลี่ยน API ในภายหลังถ้าฉันต้องการ ในกรณีที่คนตัดไม้เป็นเรื่องที่ซับซ้อนมากขึ้น

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


นอกเหนือจากนั้นคำตอบอื่น ๆ ได้ครอบคลุมคำถามหลักได้เป็นอย่างดีดังนั้นฉันแค่ต้องการที่จะมุ่งเน้นไปที่การเพิ่มที่ผ่านมาเกี่ยวกับการฉีดพึ่งพาและวัตถุจำลอง ขึ้นอยู่กับว่าเฟรมเวิร์กการบันทึกของคุณทำงานอย่างไร แต่โดยส่วนใหญ่แล้วไม่จำเป็นต้องใช้ wrapper (แม้ว่ามันอาจจะได้รับประโยชน์จากมัน) เพียงทำ API สำหรับวัตถุจำลองของคุณเหมือนกับไลบรารีบุคคลที่สามจากนั้นคุณสามารถสลับในวัตถุจำลองเพื่อทดสอบได้อย่างง่ายดาย

ปัจจัยหลักที่นี่คือไม่ว่าจะเป็นห้องสมุดของบุคคลที่สามแม้กระทั่งการดำเนินการผ่านการฉีดพึ่งพา (หรือผู้ให้บริการหรือรูปแบบบางคู่อย่างหลวม ๆ ) หากฟังก์ชั่นห้องสมุดสามารถเข้าถึงได้ด้วยวิธีการเดี่ยวหรือแบบคงที่หรือบางสิ่งบางอย่างแล้วคุณจะต้องห่อนั้นในวัตถุที่คุณสามารถทำงานกับในการฉีดพึ่งพา


1

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

ห้องสมุดบุคคลที่สามไม่ได้ออกแบบมาสำหรับความต้องการเฉพาะของเรา

และสิ่งนี้แสดงให้เห็นว่าโดยทั่วไปแล้วในรูปแบบของการทำสำเนาโค้ดเช่นนักพัฒนาที่เขียนโค้ด 8 บรรทัดเพียงเพื่อสร้างQButtonและสไตล์ในแบบที่ควรมองหาแอพพลิเคชั่นเฉพาะสำหรับนักออกแบบที่ไม่เพียง แต่ต้องการรูปลักษณ์ แต่ยังใช้งานฟังก์ชั่นของปุ่มที่จะเปลี่ยนอย่างสมบูรณ์สำหรับซอฟต์แวร์ทั้งหมดซึ่งต้องกลับไปและเขียนใหม่หลายพันบรรทัดของรหัสหรือพบว่าการปรับปรุงการเรนเดอร์ไปป์ไลน์นั้นจำเป็นต้องมีการเขียนมหากาพย์ใหม่ ไพพ์ไลน์โค้ด OpenGL ทั่วทุกสถานที่แทนที่จะรวมศูนย์การออกแบบตัวเรนเดอร์แบบเรียลไทม์และปล่อยให้การใช้ OGL อย่างเคร่งครัดสำหรับการนำไปใช้งาน

การออกแบบเหล่านี้ไม่ได้ปรับแต่งตามความต้องการการออกแบบเฉพาะของเรา พวกเขามีแนวโน้มที่จะนำเสนอสิ่งที่จำเป็นจริง ๆ (และไม่ใช่ส่วนหนึ่งของการออกแบบมีความสำคัญถ้าไม่มากไปกว่าสิ่งที่เป็น) และอินเทอร์เฟซของพวกเขาไม่ได้ออกแบบมาเพื่อตอบสนองความต้องการของเราในระดับสูง ความคิด = หนึ่งคำขอ "เรียงลำดับของวิธีที่กีดกันเราจากการควบคุมการออกแบบทั้งหมดถ้าเราใช้พวกเขาโดยตรง หากนักพัฒนาสิ้นสุดการเขียนโค้ดระดับต่ำกว่าที่ควรจะต้องแสดงสิ่งที่พวกเขาต้องการบางครั้งพวกเขาก็สามารถห่อหุ้มตัวเองในรูปแบบ Ad-hoc ที่ทำให้มันจบลงดังนั้นคุณจะจบลงด้วยการเขียนอย่างฉับพลันและหยาบช้า - เครื่องห่อหุ้มที่ออกแบบและจัดทำเอกสารแทนเครื่องห่อหุ้มที่ออกแบบมาอย่างดีและจัดทำเป็นเอกสารอย่างดี

แน่นอนว่าฉันจะใช้ข้อยกเว้นที่รัดกุมกับไลบรารีซึ่ง wrappers เป็นการแปลแบบหนึ่งต่อหนึ่งของสิ่งที่ API ของบุคคลที่สามมีให้ ในกรณีดังกล่าวอาจไม่มีการออกแบบระดับสูงที่จะแสดงความต้องการทางธุรกิจและการออกแบบโดยตรง (เช่นอาจเป็นกรณีสำหรับบางสิ่งที่คล้ายกับไลบรารี "ยูทิลิตี้") แต่ถ้ามีการออกแบบที่ปรับแต่งได้มากขึ้นซึ่งแสดงความต้องการของเราได้โดยตรงมากขึ้นฉันก็ขออยู่ในค่ายการพันเช่นเดียวกับที่ฉันชอบใช้ฟังก์ชั่นระดับสูงและนำมันกลับมาใช้ใหม่ผ่านแอสเซมบลีอินไลน์ ทั่วทุกสถานที่.

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

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


0

ความคิดของฉันเกี่ยวกับห้องสมุดบุคคลที่สาม:

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

เราควรหลีกเลี่ยงการใช้ไลบรารี UI ของบุคคลที่สามหรือไม่

เหตุผลในการพิจารณาห้องสมุดบุคคลที่สาม:

เหตุผลหลักสองประการที่นักพัฒนาพิจารณาใช้กับห้องสมุดบุคคลที่สาม:

  1. ขาดทักษะหรือความรู้ สมมติว่าคุณกำลังทำงานกับแอพแชร์รูปภาพ คุณไม่ได้เริ่มต้นด้วยการเปลี่ยน crypto ของคุณเอง
  2. ไม่มีเวลาหรือความสนใจในการสร้างบางสิ่ง หากคุณไม่มีเวลา จำกัด (ซึ่งยังไม่มีมนุษย์) คุณต้องจัดลำดับความสำคัญ

ไลบรารี UI ส่วนใหญ่ ( ไม่ใช่ทั้งหมด! ) มักจะจัดอยู่ในประเภทที่สอง สิ่งนี้ไม่ใช่วิทยาศาสตร์จรวด แต่ต้องใช้เวลาในการสร้างให้ถูกต้อง

ถ้ามันเป็นฟังก์ชั่นธุรกิจหลัก - ทำมันเองไม่ว่าจะเป็นอะไร

การควบคุม / มุมมองมีอยู่สองประเภท:

  1. ทั่วไปช่วยให้คุณสามารถใช้พวกเขาในบริบทที่แตกต่างกันจำนวนมากไม่คิดแม้แต่โดยผู้สร้างของพวกเขาเช่นจากUICollectionViewUIKit
  2. เฉพาะUIPickerViewที่ออกแบบมาสำหรับการใช้งานในกรณีเดียวเช่น ห้องสมุดบุคคลที่สามส่วนใหญ่มักตกอยู่ในประเภทที่สอง ยิ่งไปกว่านั้นพวกเขามักถูกดึงออกมาจาก codebase ที่มีอยู่ซึ่งได้รับการปรับให้เหมาะสม

สมมติฐานแรกที่ไม่รู้จัก

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

บ่อยครั้งที่การเรียนรู้ความคิดนั้นมีประโยชน์มากกว่าการได้รับโค้ดผลลัพธ์เอง

คุณไม่สามารถซ่อนมันได้

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

ค่าใช้จ่ายเวลาในอนาคต

UIKit เปลี่ยนแปลงกับ iOS ทุกรุ่น สิ่งที่จะทำลาย การพึ่งพาบุคคลที่สามของคุณจะไม่เป็นการบำรุงรักษาอย่างที่คุณคาดไว้

สรุป:

จากประสบการณ์ส่วนตัวของฉันการใช้งานส่วนใหญ่ของรหัส UI ของบุคคลที่สามจะลดลงเพื่อแลกเปลี่ยนความยืดหยุ่นที่น้อยลงเพื่อให้ได้ผลในบางครั้ง

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


0

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

ฉันคิดว่าปัญหาเหล กรณีเดียวที่ฉันจะใช้ wrapper คือเมื่อมีการวางแผนการโยกย้ายไปยังการใช้งานอื่นอย่างแน่นอนหรือ API ที่มีการห่อข้อมูลนั้นไม่แข็งพอและเปลี่ยนแปลงตลอดเวลา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.