วิธีการเดียวที่มีพารามิเตอร์จำนวนมากและวิธีการมากมายที่จะต้องเรียกตาม


16

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

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


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

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

ตัวอย่างของการดำเนินการที่ไม่ใช่การสับเปลี่ยน: การแปลงภาพ (การหมุน, การปรับสเกลและการครอบตัด), การคูณเมทริกซ์, ฯลฯ
rwong

บางทีคุณสามารถใช้การแกง: มันจะทำให้มันเป็นไปไม่ได้ที่จะใช้วิธีการ / ฟังก์ชั่นในลำดับที่ไม่ถูกต้อง
Giorgio

คุณกำลังทำงานอยู่ที่นี่ ฉันหมายความว่าฉันคิดว่ามาตรฐานคือการส่งผ่านวัตถุการแปลง (เช่นAffine Transformสำหรับ 2D ของ Java ) ที่คุณผ่านไปยังวิธีการบางอย่างที่ใช้มัน เนื้อหาของการเปลี่ยนรูปนั้นแตกต่างกันไปขึ้นอยู่กับลำดับที่คุณเรียกใช้การดำเนินการเริ่มต้นบนการออกแบบ (ดังนั้นจึงเป็น "คุณเรียกมันตามลำดับที่คุณต้องการ" ไม่ใช่ "ตามลำดับที่ฉันต้องการ")
Clockwork-Muse

คำตอบ:


24

ระวังการมีเพศสัมพันธ์ทางโลก อย่างไรก็ตามนี่ไม่ใช่ปัญหาเสมอไป

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

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

นี่คือวิธีการที่ซับซ้อนรหัสควรมอง:

public List<Widget> process(File file) throws IOException {
  try (BufferedReader in = new BufferedReader(new FileReader(file))) {
    List<Widget> widgets = new LinkedList<>();
    String line;
    while ((line = in.readLine()) != null) {
      if (isApplicable(line)) { // Filter blank lines, comments, etc.
        Ore o = preprocess(line);
        Ingot i = smelt(o);
        Alloy a = combine(i, new Nonmetal('C'));
        Widget w = smith(a);
        widgets.add(w);
      }
    }
    return widgets;
  }
}

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

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

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


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

10

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

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


5

ฉันจะสร้าง» ImageProcesssor « (หรือชื่ออะไรก็ตามที่เหมาะกับโครงการของคุณ) และวัตถุการกำหนดค่าProcessConfigurationซึ่งมีพารามิเตอร์ที่จำเป็นทั้งหมด

 ImageProcessor p = new ImageProcessor();

 ProcessConfiguration config = new processConfiguration().setTranslateX(100)
                                                         .setTranslateY(100)
                                                         .setRotationAngle(45);
 p.process(image, config);

ภายในตัวประมวลผลภาพที่คุณแค็ปซูลกระบวนการทั้งหมดที่อยู่เบื้องหลังหนึ่ง mehtod process()

public class ImageProcessor {

    public Image process(Image i, ProcessConfiguration c){
        Image processedImage=i.getCopy();
        shift(processedImage, c);
        rotate(processedImage, c);
        return processedImage;
    }

    private void rotate(Image i, ProcessConfiguration c) {
        //rotate
    }

    private void shift(Image i, ProcessConfiguration c) {
        //shift
    }
}

วิธีนี้เรียกวิธีการเปลี่ยนแปลงตามลำดับที่ถูกต้องshift(), rotate(). วิธีการแต่ละคนได้รับค่าพารามิเตอร์ที่เหมาะสมจากที่ผ่านProcessConfiguration

public class ProcessConfiguration {

    private int translateX;

    private int rotationAngle;

    public int getRotationAngle() {
        return rotationAngle;
    }

    public ProcessConfiguration setRotationAngle(int rotationAngle){
        this.rotationAngle=rotationAngle;
        return this;
    }

    public int getTranslateY() {
        return translateY;
    }

    public ProcessConfiguration setTranslateY(int translateY) {
        this.translateY = translateY;
        return this;
    }

    public int getTranslateX() {
        return translateX;
    }

    public ProcessConfiguration setTranslateX(int translateX) {
        this.translateX = translateX;
        return this;
    }

    private int translateY;

}

ฉันใช้อินเทอร์เฟซของเหลว

public ProcessConfiguration setRotationAngle(int rotationAngle){
    this.rotationAngle=rotationAngle;
    return this;
}

ซึ่งอนุญาตให้เริ่มต้นที่ดี (เท่าที่เห็นด้านบน)

ข้อได้เปรียบที่ชัดเจน encapsulating พารามิเตอร์ที่จำเป็นในวัตถุเดียว ลายเซ็นวิธีการของคุณสามารถอ่านได้:

private void shift(Image i, ProcessConfiguration c)

มันเป็นเรื่องเกี่ยวกับการขยับภาพและพารามิเตอร์รายละเอียดเป็นอย่างใดการกำหนดค่า

อีกวิธีหนึ่งคุณสามารถสร้างProcessingPipeline :

public class ProcessingPipeLine {

    Image i;

    public ProcessingPipeLine(Image i){
        this.i=i;
    };

    public ProcessingPipeLine shift(Coordinates c){
        shiftImage(c);
        return this;
    }

    public ProcessingPipeLine rotate(int a){
        rotateImage(a);
        return this;
    }

    public Image getResultingImage(){
        return i;
    }

    private void rotateImage(int angle) {
        //shift
    }

    private void shiftImage(Coordinates c) {
        //shift
    }

}

การเรียกเมธอดไปที่เมธอดprocessImageจะยกตัวอย่างไปป์ไลน์ดังกล่าวและทำให้เกิดความโปร่งใสว่าคำสั่งนั้นคืออะไร: shift , หมุน

public Image processImage(Image i, ProcessConfiguration c){
    Image processedImage=i.getCopy();
    processedImage=new ProcessingPipeLine(processedImage)
            .shift(c.getCoordinates())
            .rotate(c.getRotationAngle())
            .getResultingImage();
    return processedImage;
}

3

คุณเคยลองใช้การแกงบ้างไหม? ลองนึกภาพว่าคุณมีชั้นเรียนProcesseeและชั้นเรียนProcessor:

class Processor
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T1 a1, T2 a2)
    {
        // Process using a1
        // then process using a2
    }
}

ตอนนี้คุณสามารถแทนที่คลาสProcessorด้วยสองคลาสProcessor1และProcessor2:

class Processor1
{
    private final Processee _processee;

    public Processor1(Processee p)
    {
        _processee = p;
    }

    public Processor2 process(T1 a1)
    {
        // Process using argument a1

        return new Processor2(_processee);
    }
}

class Processor2
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T2 a2)
    {
        // Process using argument a2
    }
}

จากนั้นคุณสามารถเรียกการดำเนินการตามลำดับที่ถูกต้องโดยใช้:

new Processor1(processee).process(a1).process(a2);

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


เรามีแนวความคิดเหมือนกันเกือบ); ความแตกต่างเพียงอย่างเดียวคือว่าท่อของคุณบังคับใช้คำสั่งการประมวลผลที่เข้มงวด
โทมัสขยะ

@ThomasJunk: เท่าที่ฉันเข้าใจนี่เป็นข้อกำหนด: "วิธีการเหล่านี้จะต้องเรียกตามลำดับที่เฉพาะเจาะจงมากเพื่อให้ได้ผลลัพธ์ที่ถูกต้อง" การมีคำสั่งดำเนินการที่เข้มงวดนั้นฟังดูคล้ายกับองค์ประกอบของฟังก์ชั่น
Giorgio

เช่นเดียวกับที่ฉันทำ แต่ถ้าการประมวลผลคำสั่งซื้อเปลี่ยนไปคุณต้องทำการปรับโครงสร้างใหม่อีกครั้ง)
Thomas Junk

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