สไตล์สำหรับโฟลว์การควบคุมพร้อมการตรวจสอบความถูกต้อง


27

ฉันพบว่าตัวเองเขียนโค้ดจำนวนมากเช่นนี้:

int myFunction(Person* person) {
  int personIsValid = !(person==NULL);
  if (personIsValid) {
     // do some stuff; might be lengthy
     int myresult = whatever;
     return myResult;
  }
  else {
    return -1;
  }
}

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

int netWorth(Person* person) {
  if (Person==NULL) {
    return -1;
  }
  if (!(person->isAlive))  {
    return -1;
  }
  int assets = person->assets;
  if (assets==-1)  {
    return -1;
  }
  int liabilities = person->liabilities;
  if (liabilities==-1) {
    return -1;
  }
  return assets - liabilities;
}

ฉันสนใจในความคิดเห็นเกี่ยวกับตัวเลือกโวหารที่นี่ [ไม่ต้องกังวลมากเกินไปเกี่ยวกับรายละเอียดของข้อความส่วนตัว; มันเป็นโฟลว์การควบคุมโดยรวมที่ฉันสนใจ]


8
อนุญาตให้ฉันชี้ให้เห็นว่าคุณมีข้อผิดพลาดที่ร้ายแรงพอสมควรในตัวอย่างของคุณ ตัวอย่างเช่นหากสินทรัพย์ == 42 และหนี้สิน == 43 คุณจะประกาศบุคคลที่ไม่มีอยู่
John R. Strohm

จะไม่ส่งข้อยกเว้นและให้รหัสลูกค้าจัดการการตรวจสอบได้ดีขึ้นหรือไม่
Tulains Córdova

@ TulainsCórdovaอาจไม่มีข้อยกเว้นหรืออาจมีข้อมูลที่ไม่ถูกต้องไม่เพียงพอที่ผลกระทบต่อประสิทธิภาพของการสร้างการติดตามสแต็ก ฯลฯ เป็นที่ยอมรับ
Hulk

คำตอบ:


27

สำหรับปัญหาประเภทนี้ Martin Fowler เสนอรูปแบบข้อมูลจำเพาะ :

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

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

วิธีที่ฉันเห็นมันเป็นแนวคิดหลักคือการ "แยก" รหัสที่จะตรวจสอบลงในวิธีการเฉพาะ / วัตถุ

ด้วยnetWorthตัวอย่างของคุณสิ่งนี้อาจมีลักษณะดังนี้:

int netWorth(Person* person) {
  if (isSatisfiedBySpec(person)) {
    return person->assets - person->liabilities;
  }
  log("person doesn't satisfy spec");
  return -1;
}

#define BOOLEAN int // assuming C here
BOOLEAN isSatisfiedBySpec(Person* person) {
  return Person != NULL
      && person->isAlive
      && person->assets != -1
      && person->liabilities != -1;
}

กรณีของคุณจะค่อนข้างง่ายเพื่อให้การตรวจสอบทั้งหมดดูตกลงเพื่อให้พอดีกับรายการธรรมดาภายในวิธีการเดียว ฉันมักจะต้องแบ่งวิธีการเพิ่มเติมเพื่อให้อ่านดีขึ้น

ฉันยังมักจะจัดกลุ่ม / แยก "ข้อมูลจำเพาะ" วิธีการที่เกี่ยวข้องในวัตถุเฉพาะแม้ว่ากรณีของคุณดูตกลงโดยที่

  // ...
  Specification s, *spec = initialize(s, person);
  if (spec->isSatisfied()) {
    return person->assets - person->liabilities;
  }
  log("person doesn't satisfy spec");
  return -1;
  // ...

คำถามที่กองมากเกินนี้แนะนำให้เชื่อมโยงน้อยนอกเหนือจากการอย่างใดอย่างหนึ่งดังกล่าวข้างต้น: ข้อมูลจำเพาะแบบตัวอย่าง โดยเฉพาะอย่างยิ่งคำตอบแนะนำDimecasts 'การเรียนรู้รูปแบบจำเพาะสำหรับคำแนะนำของตัวอย่างและกล่าวถึง"รายละเอียด" กระดาษเขียนโดยเอริคอีแวนส์และมาร์ตินฟาวเลอร์


8

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

int netWorth(Person* person) { 
    if(validPerson(person)) {
        int assets = person->assets;
        int liabilities = person->liabilities;
        return assets - liabilities;
    }
    else {
        return -1;
    }
}

bool validPerson(Person* person) { 
    if(person!=NULL && person->isAlive
      && person->assets !=-1 && person->liabilities != -1)
        return true;
    else
        return false;
}

2
ทำไมคุณถึงมีifในvalidPerson? เพียงแค่กลับมาperson!=NULL && person->isAlive && person->assets !=-1 && person->liabilities != -1แทน
David Hammen

3

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

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

พิจารณาการทำตัวอย่างของคุณใหม่นี้:

int myFunction(Person* person) {
  int personIsValid = !(person==NULL);
  if (personIsValid) {
     return myFunctionWork(person)
  }
  else {
    return -1;
  }
}

int myFunction(Person *person) {
  assert( person != NULL);  
  // Do work and return
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.