หากแบบจำลองนี้ตรวจสอบความถูกต้องของข้อมูลมันไม่ควรโยนข้อยกเว้นเกี่ยวกับอินพุตที่ไม่ดี?


9

การอ่านคำถาม SOนี้ดูเหมือนว่าการโยนข้อยกเว้นสำหรับการตรวจสอบความถูกต้องของข้อมูลเข้าของผู้ใช้

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

หลักฐานของฉันที่การตรวจสอบควรทำในชั้นธุรกิจผิดหรือเปล่า?

สิ่งที่ฉันทำ

ดังนั้นรหัสของฉันมักจะจบลงเช่นนี้

<?php
class Person
{
  private $name;
  private $age;

  public function setName($n) {
    $n = trim($n);
    if (mb_strlen($n) == 0) {
      throw new ValidationException("Name cannot be empty");
    }
    $this->name = $n;
  }

  public function setAge($a) {
    if (!is_int($a)) {
      if (!ctype_digit(trim($a))) {
        throw new ValidationException("Age $a is not valid");
      }
      $a = (int)$a;
    }
    if ($a < 0 || $a > 150) {
      throw new ValidationException("Age $a is out of bounds");
    }
    $this->age = $a;
  }

  // other getters, setters and methods
}

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

<?php
$person = new Person();
$errors = array();

// global try for all exceptions other than ValidationException
try {

  // validation and process (if everything ok)
  try {
    $person->setAge($_POST['age']);
  } catch (ValidationException $e) {
    $errors['age'] = $e->getMessage();
  }

  try {
    $person->setName($_POST['name']);
  } catch (ValidationException $e) {
    $errors['name'] = $e->getMessage();
  }

  ...
} catch (Exception $e) {
  // log the error, send 500 internal server error to the client
  // and finish the request
}

if (count($errors) == 0) {
  // process
} else {
  showErrorsToUser($errors);
}

นี่เป็นวิธีการที่ไม่ดีหรือไม่?

วิธีอื่น

ฉันควรสร้างวิธีการisValidAge($a)ส่งคืนจริง / เท็จแล้วเรียกพวกเขาจากตัวควบคุม?

<?php
class Person
{
  private $name;
  private $age;

  public function setName($n) {
    $n = trim($n);
    if ($this->isValidName($n)) {
      $this->name = $n;
    } else {
      throw new Exception("Invalid name");
    }
  }

  public function setAge($a) {
    if ($this->isValidAge($a)) {
      $this->age = $a;
    } else {
      throw new Exception("Invalid age");
    }
  }

  public function isValidName($n) {
    $n = trim($n);
    if (mb_strlen($n) == 0) {
      return false;
    }
    return true;
  }

  public function isValidAge($a) {
    if (!is_int($a)) {
      if (!ctype_digit(trim($a))) {
        return false;
      }
      $a = (int)$a;
    }
    if ($a < 0 || $a > 150) {
      return false;
    }
    return true;
  }

  // other getters, setters and methods
}

และคอนโทรลเลอร์จะเหมือนกันโดยทั่วไปแทนที่จะลอง / จับตอนนี้ถ้า / อื่น ๆ :

<?php
$person = new Person();
$errors = array();
if ($person->isValidAge($age)) {
  $person->setAge($age);
} catch (Exception $e) {
  $errors['age'] = "Invalid age";
}

if ($person->isValidName($name)) {
  $person->setName($name);
} catch (Exception $e) {
  $errors['name'] = "Invalid name";
}

...

if (count($errors) == 0) {
  // process
} else {
  showErrorsToUser($errors);
}

ดังนั้นฉันควรทำอย่างไร

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


ฉันได้แก้ไขโค้ด "ดั้งเดิม" นิดหน่อยเพื่อจัดการValidationExceptionและข้อยกเว้นอื่น ๆ
Carlos Campderrós

2
ปัญหาหนึ่งในการแสดงข้อความข้อยกเว้นแก่ผู้ใช้คือโมเดลนั้นต้องรู้ว่าผู้ใช้พูดภาษาใด แต่ส่วนใหญ่เป็นปัญหาของมุมมอง
Bart van Ingen Schenau

@ BartvanIngenSchenau จับได้ดี แอปพลิเคชันของฉันเป็นภาษาเดียวเสมอ แต่ก็ดีที่คิดในปัญหาการแปลที่อาจเกิดขึ้นในการดำเนินการใด ๆ
Carlos Campderrós

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

คำตอบ:


7

วิธีการที่ฉันใช้ในอดีตคือการใส่ตรรกะการตรวจสอบทั้งหมดเฉพาะชั้นการตรวจสอบ

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

ทำตามวิธีการนี้แล้วคุณสามารถปฏิบัติกับข้อผิดพลาดในการตรวจสอบความถูกต้องแตกต่างกันไปตามชั้นที่เกิดขึ้นใน:

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

ดังนั้นคุณมีคลาสที่PersonValidatorมีตรรกะทั้งหมดเพื่อตรวจสอบคุณลักษณะที่แตกต่างกันของ a PersonและPersonคลาสที่ขึ้นอยู่กับสิ่งนี้PersonValidatorใช่ไหม ข้อเสนอของคุณมีประโยชน์อย่างไรกับวิธีอื่นที่ฉันแนะนำในคำถาม ฉันเห็นความสามารถในการฉีดคลาสการตรวจสอบที่แตกต่างกันสำหรับ a Personเท่านั้น แต่ฉันไม่สามารถคิดได้ว่าจะต้องใช้กรณีใด
Carlos Campderrós

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

สำหรับแอปพลิเคชันที่คุณวางแผนที่จะขายให้กับหลาย ๆ คน / บริษัท อาจมีเหตุผลเพราะแต่ละ บริษัท อาจมีกฎที่แตกต่างกันในการตรวจสอบว่าช่วงที่ถูกต้องสำหรับอายุของบุคคลนั้นคืออะไร ดังนั้นจึงเป็นไปได้มีประโยชน์ แต่จริง ๆ แล้ว overkill สำหรับความต้องการของฉัน อย่างไรก็ตาม +1 สำหรับคุณเช่นกัน
Carlos Campderrós

1
การแยกการตรวจสอบความถูกต้องจากแบบจำลองทำให้รู้สึกได้จากจุดเชื่อมต่อและการทำงานร่วมกันเช่นกัน ในสถานการณ์ง่าย ๆ นี้มันอาจ overkill แต่จะใช้กฎการตรวจสอบ "ข้ามสนาม" เดียวเท่านั้นที่จะทำให้คลาส Validator แยกต่างหากน่าสนใจยิ่งขึ้น
เซท M.

8

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

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

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


ขณะนี้มีหลายคนที่พูดถึงสิ่งต่าง ๆ เช่น "ข้อยกเว้นควรใช้สำหรับสิ่งพิเศษเท่านั้นและ XYZ ไม่ได้ยอดเยี่ยม" (ตัวอย่างเช่นคำตอบของ @dann1111 ... ซึ่งเขาระบุข้อผิดพลาดของผู้ใช้เป็น "ปกติอย่างสมบูรณ์")

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

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

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


ในระยะสั้น - การตัดสินใจที่จะใช้หรือไม่ใช้ข้อยกเว้นควรทำหลังจากชั่งน้ำหนักข้อดี ... และไม่ควรอยู่บนพื้นฐานของหลักการง่ายๆ


จุดดีเกี่ยวกับExceptionการโยน / จับทั่วไป ฉันโยนคลาสย่อยของตัวเองจริงๆแล้วExceptionรหัสของ setters มักจะไม่ทำอะไรเลย
Carlos Campderrós

ฉันได้แก้ไขโค้ด "ดั้งเดิม" เล็กน้อยเพื่อรองรับ ValidationException และข้อยกเว้นอื่น ๆ / cc @ dan1111
Carlos Campderrós

1
+1, ฉันค่อนข้างจะมีคำอธิบาย ValidationException มากกว่ากลับไปสู่ยุคมืดของการตรวจสอบค่าตอบแทนของการโทรทุกวิธี รหัสง่าย = ข้อผิดพลาดที่อาจเกิดขึ้นน้อย
Heinzi

2
@ dan1111 - ในขณะที่ผมเคารพสิทธิของคุณมีความเห็นอะไรในความคิดเห็นของคุณคืออะไรอื่น ๆมากกว่าความคิดเห็น ไม่มีการเชื่อมต่อแบบลอจิคัลระหว่าง "ปกติ" ของการตรวจสอบและกลไกสำหรับการจัดการข้อผิดพลาดการตรวจสอบ สิ่งที่คุณกำลังทำคือการอ่านความเชื่อ
Stephen C

@StephenC ตามการไตร่ตรองฉันรู้สึกว่าฉันได้ระบุกรณีของฉันอย่างยิ่ง ฉันยอมรับว่ามันเป็นเรื่องส่วนตัวมากกว่า

6

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

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

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

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

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

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

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

รหัสเดิมของคุณมีปัญหา:

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

รหัสทางเลือกยังมีปัญหา:

  • isValidAge()มีการแนะนำวิธีการพิเศษและไม่จำเป็น
  • ตอนนี้setAge()วิธีการจะต้องสมมติว่าผู้โทรตรวจสอบแล้วisValidAge()(ข้อสมมติฐานแย่มาก) หรือตรวจสอบอายุอีกครั้ง หากตรวจสอบอายุอีกครั้งsetAge() ยังคงต้องจัดการข้อผิดพลาดบางประเภทและคุณจะกลับไปที่ตารางอีกครั้ง

การออกแบบที่แนะนำ

  • ทำให้setAge()ผลตอบแทนเป็นจริงเมื่อประสบความสำเร็จและเป็นเท็จเมื่อล้มเหลว

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


ถ้าอย่างนั้นฉันควรทำยังไงดี? ด้วยวิธีสำรองที่ฉันเสนอหรือแตกต่างอย่างสิ้นเชิงกับที่ฉันไม่เคยคิด? นอกจากนี้ยังเป็นหลักฐานของฉันที่ว่า "การตรวจสอบควรจะทำในชั้นธุรกิจ" เท็จ?
Carlos Campderrós

@ CarlosCampderrósดูการอัปเดต; ฉันกำลังเพิ่มข้อมูลนั้นในขณะที่คุณแสดงความคิดเห็น การออกแบบดั้งเดิมของคุณมีการตรวจสอบความถูกต้องในสถานที่ที่ถูกต้อง แต่เป็นความผิดพลาดที่จะใช้ข้อยกเว้นเพื่อทำการตรวจสอบความถูกต้องนั้น

วิธีการอื่นจะบังคับให้การsetAgeตรวจสอบอีกครั้ง แต่เป็นตรรกะโดยทั่วไป "ถ้ามันถูกต้องแล้วกำหนดอายุอื่นโยนข้อยกเว้น" มันไม่ได้พาฉันกลับไปที่ตารางหนึ่ง
Carlos Campderrós

2
ปัญหาหนึ่งที่ฉันเห็นทั้งสองวิธีอื่นและการออกแบบที่แนะนำคือพวกเขาสูญเสียความสามารถในการแยกแยะว่าทำไมอายุนั้นไม่ถูกต้อง มันอาจถูกสร้างขึ้นเพื่อคืนค่าจริงหรือสตริงข้อผิดพลาด (ใช่ php สกปรกมาก) แต่สิ่งนี้อาจนำไปสู่ปัญหามากมายเพราะ"The entered age is out of bounds" == trueและผู้คนควรใช้ตลอดเวลา===ดังนั้นวิธีการนี้จะเป็นปัญหามากกว่าปัญหาที่พยายามทำ แก้ปัญหา
Carlos Campderrós

2
แต่การเข้ารหัสแอปพลิเคชันนั้นน่าเบื่อจริงๆเพราะสำหรับทุกsetAge()สิ่งที่คุณทำทุกที่คุณต้องตรวจสอบว่ามันใช้งานได้จริง การโยนข้อยกเว้นหมายความว่าคุณไม่ควรกังวลเกี่ยวกับการจำเพื่อตรวจสอบทุกอย่างได้ดี ขณะที่ผมเห็นมันพยายามที่จะตั้งค่าไม่ถูกต้องเข้ามาในแอตทริบิวต์ / Exceptionสถานที่ให้บริการเป็นสิ่งที่โดดเด่นและแล้วมูลค่าการขว้างปา แบบจำลองไม่ควรสนใจว่าจะได้รับข้อมูลจากฐานข้อมูลหรือจากผู้ใช้ ไม่ควรได้รับอินพุตที่ไม่ดีดังนั้นฉันจึงเห็นว่าถูกต้องตามกฎหมายที่จะโยนข้อยกเว้นที่นั่น
Carlos Campderrós

4

จากมุมมองของฉัน (ฉันเป็นคนที่แต่งตัวประหลาด Java) มันถูกต้องสมบูรณ์วิธีที่คุณใช้มันเป็นวิธีแรก

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

แม้ว่าคุณควรระบุ @Stephen C ไว้แล้วยกเว้นข้อผิดพลาดของคุณเอง

อย่างไรก็ตามอีกวิธีหนึ่งคือการใช้Data Transfer Objectsซึ่งเป็นเพียง data-container โดยไม่มีเหตุผลใด ๆ จากนั้นคุณส่งมอบDTOเช่นนี้ไปยังผู้ตรวจสอบความถูกต้องหรือ Model-Object เองเพื่อการตรวจสอบความถูกต้องและหากสำเร็จแล้วให้ทำการอัปเดตใน Model-Object วิธีการนี้มักใช้เมื่อการนำเสนอตรรกะและตรรกะของแอปพลิเคชันแยกชั้น (งานนำเสนอคือหน้าเว็บแอปพลิเคชันและเว็บเซอร์) วิธีนี้พวกเขาจะถูกแยกทางกายภาพ แต่ถ้าคุณมีทั้งสองในหนึ่งระดับ (เช่นในตัวอย่างของคุณ) คุณต้องให้แน่ใจว่าจะไม่มีวิธีแก้ปัญหาเพื่อตั้งค่าโดยไม่ต้องตรวจสอบ


4

เมื่อสวมหมวก Haskell ของฉันทั้งสองวิธีนี้ผิด

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

บุคคลนั้นมีค่าคงที่เช่นค่าชื่อและอายุ

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

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

น่าเสียดายที่ Java, PHP และภาษา OO อื่น ๆ ทำให้ตัวเลือกที่ถูกต้องสวย ใน Java APIs ที่เหมาะสมมักจะใช้วัตถุตัวสร้าง ใน API ดังกล่าวการสร้างบุคคลจะมีลักษณะดังนี้:

Person p = new Person.Builder().setName(name).setAge(age).build();

หรือ verbose เพิ่มเติม:

Person.Builder builder = new Person.Builder();
builder.setName(name);
builder.setAge(age);
Person p = builder.build();
// Person object must have name and age here

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


สิ่งที่คุณทำที่นี่คือย้ายปัญหาไปยังคลาส Builder ซึ่งคุณยังไม่ได้รับคำตอบจริงๆ
Cypher

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

มันยังไม่ตอบคำถาม (อย่างน้อยก็ไม่สมบูรณ์) คุณสามารถอธิบายรายละเอียดเกี่ยวกับวิธีส่งข้อความแสดงข้อผิดพลาดแต่ละรายการจากคลาส Builder ถึง call stack ไปยัง View ได้อย่างไร
Cypher

สามความเป็นไปได้: build () สามารถสร้างข้อยกเว้นเฉพาะเช่นในตัวอย่างแรกของ OP อาจมีการตรวจสอบชุด <String> สาธารณะที่ส่งคืนชุดข้อผิดพลาดที่มนุษย์สามารถอ่านได้ จะมีการตรวจสอบชุด <Error> สาธารณะสำหรับข้อผิดพลาดที่พร้อมสำหรับ i18n ประเด็นคือสิ่งนี้เกิดขึ้นระหว่างการแปลงเป็นวัตถุบุคคล
user239558

2

ในคำพูดของคนธรรมดา:

วิธีแรกคือวิธีที่ถูกต้อง

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

ชั้นธุรกิจจะต้องโยนข้อยกเว้นทุกครั้งที่มีการละเมิดกฎธุรกิจ

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

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

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