เหตุผลที่ต้องการตัวแปรท้องถิ่นมากกว่าตัวแปรอินสแตนซ์?


109

codebase ที่ฉันใช้บ่อยใช้ตัวแปรอินสแตนซ์เพื่อแชร์ข้อมูลระหว่างวิธีการต่าง ๆ นักพัฒนาดั้งเดิมยืนยันว่านี่เป็นไปตามแนวทางปฏิบัติที่ดีที่สุดที่ระบุไว้ในหนังสือClean Codeของลุงบ๊อบ / โรเบิร์ตมาร์ติน: "กฎข้อแรกของฟังก์ชั่นคือพวกเขาควรมีขนาดเล็ก" และ "จำนวนที่เหมาะสมของการขัดแย้งสำหรับฟังก์ชั่นคือศูนย์ (niladic) (... ) การโต้เถียงนั้นยากพวกเขาใช้พลังทางแนวคิดมากมาย"

ตัวอย่าง:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  private byte[] encodedData;
  private EncryptionInfo encryptionInfo;
  private EncryptedObject payloadOfResponse;
  private URI destinationURI;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    getEncodedData(encryptedRequest);
    getEncryptionInfo();
    getDestinationURI();
    passRequestToServiceClient();

    return cryptoService.encryptResponse(payloadOfResponse);
  }

  private void getEncodedData(EncryptedRequest encryptedRequest) {
    encodedData = cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private void getEncryptionInfo() {
    encryptionInfo = cryptoService.getEncryptionInfoForDefaultClient();
  }

  private void getDestinationURI() {
    destinationURI = router.getDestination().getUri();
  }

  private void passRequestToServiceClient() {
    payloadOfResponse = serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }
}

ฉันจะ refactor ที่เป็นต่อไปนี้โดยใช้ตัวแปรท้องถิ่น:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    byte[] encodedData = cryptoService.decryptRequest(encryptedRequest, byte[].class);
    EncryptionInfo encryptionInfo = cryptoService.getEncryptionInfoForDefaultClient();
    URI destinationURI = router.getDestination().getUri();
    EncryptedObject payloadOfResponse = serviceClient.handle(destinationURI, encodedData,
      encryptionInfo);

    return cryptoService.encryptResponse(payloadOfResponse);
  }
}

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

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

และในทางกลับกันมีข้อเสียสำหรับการเปลี่ยนโฉมใหม่ที่ฉันอาจมองข้ามหรือไม่?


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
maple_shaft

คำตอบ:


170

เหตุผลทางวิทยาศาสตร์วัตถุประสงค์เพื่อสนับสนุนตัวแปรท้องถิ่นมากกว่าตัวแปรอินสแตนซ์คืออะไร?

ขอบเขตไม่ใช่สถานะไบนารีมันเป็นทางลาด คุณสามารถจัดอันดับเหล่านี้จากมากที่สุดไปหาน้อยที่สุด:

Global > Class > Local (method) > Local (code block, e.g. if, for, ...)

แก้ไข: สิ่งที่ฉันเรียกว่า "ขอบเขตของคลาส" คือสิ่งที่คุณหมายถึงโดย "ตัวแปรอินสแตนซ์" สำหรับความรู้ของฉันมันมีความหมายเหมือนกัน แต่ฉันเป็น C # dev ไม่ใช่ Java dev เพื่อประโยชน์ของความกะทัดรัดฉันได้รวบรวมสถิติทั้งหมดลงในหมวดหมู่ทั่วโลกเนื่องจากสถิตศาสตร์ไม่ใช่หัวข้อของคำถาม

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

  • มันบังคับให้คุณคิดเกี่ยวกับความรับผิดชอบของชั้นเรียนปัจจุบันและช่วยให้คุณติดกับ SRP
  • จะช่วยให้คุณไม่จำเป็นต้องหลีกเลี่ยงความขัดแย้งการตั้งชื่อระดับโลกเช่นถ้าสองคนหรือมากกว่าชั้นเรียนมีNameคุณสมบัติที่คุณไม่ได้บังคับให้นำหน้าพวกเขาเช่นFooName, BarName... ดังนั้นการรักษาชื่อตัวแปรของคุณเป็นที่สะอาดและสั้นที่สุดเท่าที่ทำได้
  • มันประกาศโค้ดโดย จำกัด ตัวแปรที่มีอยู่ (เช่นสำหรับ Intellisense) ให้กับตัวแปรที่เกี่ยวข้องกับบริบท
  • มันเปิดใช้งานรูปแบบการควบคุมการเข้าถึงบางอย่างเพื่อไม่ให้ข้อมูลของคุณถูกควบคุมโดยนักแสดงบางคนที่คุณไม่รู้จัก (เช่นชั้นเรียนอื่นที่พัฒนาโดยเพื่อนร่วมงาน)
  • มันทำให้โค้ดสามารถอ่านได้มากขึ้นในขณะที่คุณมั่นใจว่าการประกาศตัวแปรเหล่านี้พยายามที่จะอยู่ใกล้เคียงกับการใช้งานจริงของตัวแปรเหล่านี้เท่าที่จะทำได้
  • การประกาศตัวแปรในขอบเขตที่กว้างเกินไปอย่างซุกซนมักจะบ่งบอกถึงนักพัฒนาที่ไม่ค่อยเข้าใจ OOP หรือวิธีการนำไปใช้ การเห็นตัวแปรที่มีการกำหนดขอบเขตอย่างกว้างขวางมากเกินไปจะทำหน้าที่เป็นธงสีแดงซึ่งอาจมีบางอย่างผิดปกติกับวิธีการของ OOP (โดยเฉพาะกับนักพัฒนาโดยทั่วไปหรือรหัสฐานโดยเฉพาะ)
  • (ความเห็นโดย Kevin) การใช้คนในพื้นที่บังคับให้คุณทำสิ่งต่าง ๆ ในลำดับที่ถูกต้อง ในรหัสดั้งเดิม (ตัวแปรคลาส) คุณสามารถย้ายpassRequestToServiceClient()ไปยังด้านบนของวิธีการอย่างผิดพลาดและมันจะยังคงรวบรวม ด้วยคนในท้องถิ่นคุณสามารถทำผิดพลาดได้หากคุณผ่านตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นซึ่งหวังว่าจะชัดเจนพอที่คุณจะไม่ทำมัน

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

และในทางกลับกันมีข้อเสียสำหรับการเปลี่ยนโฉมใหม่ที่ฉันอาจมองข้ามหรือไม่?

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

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

private byte[] getEncodedData() {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
}

private EncryptionInfo getEncryptionInfo() {
    return cryptoService.getEncryptionInfoForDefaultClient();
}

// and so on...

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

public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    byte[] encodedData = getEncodedData();
    EncryptionInfo encryptionInfo = getEncryptionInfo();

    //and so on...

    return cryptoService.encryptResponse(payloadOfResponse);
}

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

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


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
maple_shaft

79

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


20
เห็นด้วยอย่างสิ้นเชิง! ตัวแปรสมาชิกเหล่านั้นเป็นเพียงการขัดแย้งกันของฟังก์ชัน ในความเป็นจริงมันแย่ลงตั้งแต่ตอนนี้ไม่มีการเชื่อมโยงที่ชัดเจนระหว่างค่าของตัวแปรเหล่านั้นและการใช้งานฟังก์ชั่น (จาก POV ภายนอก)
Rémi

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

1
@Qwertie หากคุณมีวัตถุข้อมูลที่ใช้งานอาจถูกห่อหุ้มอยู่ภายใน ฟังก์ชั่นที่ดูเหมือนprocess.Start();หรือmyString.ToLowerCase()ไม่น่าจะแปลกเกินไป (และเป็นเรื่องที่เข้าใจง่ายที่สุด)
R. Schmitz

5
ทั้งสองมีหนึ่งอาร์กิวเมนต์: thisนัย บางคนอาจโต้เถียงเรื่องนี้ได้รับอย่างชัดเจน - ก่อนจุด
BlackJack

47

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

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

นั่นควรจะง่าย เพียงชี้เขาไปที่คำพูดต่อไปนี้ใน Clean Code ของลุงบ็อบ:

ไม่มีผลข้างเคียง

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

(ละเว้นตัวอย่าง)

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

นั่นคือลุงบ๊อบไม่เพียง แต่บอกว่าฟังก์ชั่นควรมีการโต้แย้งน้อยเขายังบอกว่าฟังก์ชั่นควรหลีกเลี่ยงการโต้ตอบกับรัฐที่ไม่ใช่ในพื้นที่เมื่อใดก็ตามที่เป็นไปได้


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

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

1
อาเก่าดี "ในสัจพจน์สัจพจน์มันเป็นไปได้ที่จะพิสูจน์อะไร" เนื่องจากไม่มีความจริงที่ยากและโกหก IRL dogmas ใด ๆ จะต้องรวมคำแถลงที่กล่าวถึงสิ่งที่ตรงกันข้ามนั่นคือขัดแย้งกัน
ivan_pozdeev

26

"มันขัดแย้งกับสิ่งที่ลุงของใครบางคนคิดว่า" ไม่เคยโต้แย้งที่ดี ไม่เคย อย่าเอาสติปัญญาจากลุงอย่าคิดเอง

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

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

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

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

(พลาดจุดสำคัญที่ใช้ตัวแปรท้องถิ่นบังคับให้คุณโทรออกตามลำดับที่ถูกต้อง)


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

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

10
@ChristianHackl: ฉันอยู่บนเรืออย่างเต็มที่โดยที่ไม่ได้ติดตามลัทธิดั้งเดิม แต่ฉันก็ไม่ได้มองข้ามมันอย่างเด็ดขาด คำตอบที่ดูเหมือนจะแนะนำให้หลีกเลี่ยงความรู้ที่ได้รับในมุมมองของคุณและนั่นไม่ใช่วิธีที่ดีต่อสุขภาพ สุ่มสี่สุ่มห้าติดตามความคิดเห็นของคนอื่นไม่ การซักถามบางอย่างเมื่อคุณไม่เห็นด้วยใช่ เอาออกทันทีเพราะคุณไม่เห็นด้วยไม่มี ในขณะที่ฉันอ่านมันย่อหน้าแรกของคำตอบอย่างน้อยดูเหมือนจะแนะนำอย่างหลัง มันขึ้นอยู่กับความหมายของ "gnasher" กับ "คิดเอง" ซึ่งจำเป็นต้องทำอย่างละเอียด
Flater

9
บนแพลตฟอร์มการแบ่งปันภูมิปัญญาดูเหมือนว่าจะออกนอกสถานที่เล็ก ๆ น้อย ๆ ...
drjpizzle

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

14

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

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

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

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


7
จนถึงตอนนี้เป็นคำตอบเดียวที่กล่าวถึงความปลอดภัยของเธรดและการทำงานพร้อมกัน นั่นเป็นเรื่องที่น่าอัศจรรย์มากที่ได้รับตัวอย่างรหัสเฉพาะในคำถาม: ตัวอย่างของ SomeBusinessProcess ไม่สามารถดำเนินการกับคำขอที่เข้ารหัสหลายครั้งได้อย่างปลอดภัย วิธีการpublic EncryptedResponse process(EncryptedRequest encryptedRequest)ไม่ได้รับการซิงโครไนซ์และการโทรพร้อมกันอาจเป็นไปได้ที่จะปิดบังค่าของตัวแปรอินสแตนซ์ นี่เป็นจุดที่ดีที่จะนำขึ้นมา
Joshua Taylor

9

การอภิปรายเพียงprocess(...)ตัวอย่างเพื่อนร่วมงานของคุณนั้นชัดเจนมากในแง่ของตรรกะทางธุรกิจ ตรงกันข้ามตัวอย่างเคาน์เตอร์ของคุณใช้เวลามากกว่าคร่าวๆคร่าวๆเพื่อดึงความหมายใด ๆ

อย่างที่กล่าวไว้ว่าโค้ดที่สะอาดนั้นมีทั้งชัดเจนและมีคุณภาพดี - การผลักดันให้รัฐในพื้นที่มีพื้นที่ทั่วโลกมากขึ้นเป็นเพียงการประกอบในระดับสูงดังนั้นศูนย์คุณภาพ

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest request) {
    checkNotNull(encryptedRequest);

    return encryptResponse
      (routeTo
         ( destination()
         , requestData(request)
         , destinationEncryption()
         )
      );
  }

  private byte[] requestData(EncryptedRequest encryptedRequest) {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private EncryptionInfo destinationEncryption() {
    return cryptoService.getEncryptionInfoForDefaultClient();
  }

  private URI destination() {
    return router.getDestination().getUri();
  }

  private EncryptedObject routeTo(URI destinationURI, byte[] encodedData, EncryptionInfo encryptionInfo) {
    return serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }

  private void encryptResponse(EncryptedObject payloadOfResponse) {
    return cryptoService.encryptResponse(payloadOfResponse);
  }
}

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

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


4
การกำจัดตัวแปรทั้งหมดไม่จำเป็นต้องทำให้อ่านรหัสง่ายขึ้น
Pharap

กำจัดตัวแปรทั้งหมดอย่างสมบูรณ์ด้วยรูปแบบที่ไม่มีจุดหมายen.wikipedia.org/wiki/Tacit_programming
Marcin

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

7

ฉันจะลบตัวแปรเหล่านี้และวิธีส่วนตัวทั้งหมด นี่คือ refactor ของฉัน:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    return cryptoService.encryptResponse(
        serviceClient.handle(router.getDestination().getUri(),
        cryptoService.decryptRequest(encryptedRequest, byte[].class),
        cryptoService.getEncryptionInfoForDefaultClient()));
  }
}

สำหรับวิธีการส่วนตัวเช่น เป็นที่ชัดเจนและสามารถอ่านได้มากกว่าrouter.getDestination().getUri() getDestinationURI()ฉันอาจจะทำซ้ำว่าถ้าฉันใช้บรรทัดเดียวกันสองครั้งในชั้นเรียนเดียวกัน หากต้องการดูอีกวิธีหนึ่งถ้ามีความต้องการ a getDestinationURI()ก็อาจเป็นของคลาสอื่นไม่ใช่ในSomeBusinessProcessคลาส

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

อย่างไรก็ตามคลาสต้องทำprocess()แล้ววัตถุจะถูกโยนทิ้งไปโดยไม่จำเป็นต้องเก็บสถานะใด ๆ ไว้ในหน่วยความจำ ศักยภาพของ refactor เพิ่มเติมคือการนำ CryptoService ออกจากคลาสนั้น

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

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;

  public Response process(Request request) {
    return serviceClient.handle(router.getDestination().getUri());
  }
}

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

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


upvote ของฉันแม้ว่าหลายคนจะลังเลที่นี่ แน่นอนว่ามีบาง abstractions เข้าท่า (BTW วิธีที่เรียกว่า "กระบวนการ" นั้นแย่มาก) แต่ที่นี่ตรรกะมีน้อยที่สุด อย่างไรก็ตามคำถามของ OP อยู่ในรูปแบบของรหัสทั้งหมดและในกรณีนี้อาจมีความซับซ้อนมากกว่า
Joop Eggen

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

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

@JoopEggen ใช่ abstractions เข้าท่า ในตัวอย่างวิธีการส่วนตัวไม่ได้ให้อะไรที่เป็นนามธรรมอยู่แล้วผู้ใช้ในชั้นเรียนไม่รู้จักเกี่ยวกับพวกเขาด้วย
imel96

1
@ imel96 เป็นเรื่องตลกที่คุณอาจเป็นหนึ่งในไม่กี่คนที่สังเกตว่าการมีเพศสัมพันธ์ระหว่าง ServiceClient และ CryptoService นั้นคุ้มค่าที่จะให้ความสำคัญกับการฉีด CS เข้า SC แทนที่จะเป็น SBP แก้ปัญหาพื้นฐานในระดับสถาปัตยกรรมที่สูงขึ้น ... นั่นคือประเด็นหลักของเรื่องนี้ IMVHO; มันง่ายเกินไปที่จะติดตามภาพรวมในขณะที่เน้นรายละเอียด
vaxquis

4

คำตอบของ Flaterครอบคลุมประเด็นเรื่องการกำหนดขอบเขตค่อนข้างดี แต่ฉันคิดว่ามันมีอีกประเด็นที่นี่เช่นกัน

โปรดทราบว่ามีความแตกต่างระหว่างฟังก์ชั่นที่ประมวลผลข้อมูลและฟังก์ชั่นที่เข้าถึงข้อมูลได้ง่าย

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

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

ด้วยการรักษาเพียงตรรกะทางธุรกิจในฟังก์ชั่นที่มีชื่อเราจะได้รับสิ่งที่ดีที่สุดทั้งสองโลก (อยู่ระหว่างคำตอบของ Flaterและคำตอบของ imel96 ):

public EncryptedResponse process(EncryptedRequest encryptedRequest) {

    byte[] requestData = decryptRequest(encryptedRequest);
    EncryptedObject responseData = handleRequest(router.getDestination().getUri(), requestData, cryptoService.getEncryptionInfoForDefaultClient());
    EncryptedResponse response = encryptResponse(responseData);

    return response;
}

// define: decryptRequest(), handleRequest(), encryptResponse()

3

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

แนวคิดทั้งหมดของ Clean Code คือการปรับปรุงความสามารถในการอ่านและหลีกเลี่ยงข้อผิดพลาด มีกฎหลายข้อที่ละเมิดซึ่งกันและกัน

ข้อโต้แย้งของเขาต่อฟังก์ชั่นคือฟังก์ชั่นแบบ niladic นั้นดีที่สุด แต่สามารถยอมรับได้ถึงสามพารามิเตอร์ โดยส่วนตัวฉันคิดว่า 4 ก็โอเค

เมื่อมีการใช้ตัวแปรอินสแตนซ์พวกเขาควรสร้างคลาสที่เชื่อมโยงกัน นั่นหมายความว่าควรใช้ตัวแปรในหลาย ๆ วิธีถ้าไม่ใช่ทุกวิธีที่ไม่คงที่

ควรย้ายตัวแปรที่ไม่ได้ใช้ในหลายสถานที่ของชั้นเรียน

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


1

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

ตัวแปรอินสแตนซ์ลดวิธีการที่เรียกใช้ฟังก์ชันซึ่งช่วยลดข้อผิดพลาดบางประเภทและปรับปรุงความสามารถในการอ่าน

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

TL; DR: ฉันคิดว่าเหตุผลที่คุณได้รับความกระตือรือร้นมากเกินไปคือความกระตือรือร้นที่มากเกินไป


0

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

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    return getEncryptedResponse(
            passRequestToServiceClient(getDestinationURI(), getEncodedData(encryptedRequest) getEncryptionInfo())
        );
  }

  private EncryptedResponse getEncryptedResponse(EncryptedObject encryptedObject) {
    return cryptoService.encryptResponse(encryptedObject);
  }

  private byte[] getEncodedData(EncryptedRequest encryptedRequest) {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private EncryptionInfo getEncryptionInfo() {
    return cryptoService.getEncryptionInfoForDefaultClient();
  }

  private URI getDestinationURI() {
    return router.getDestination().getUri();
  }

  private EncryptedObject passRequestToServiceClient(URI destinationURI, byte[] encodedData, EncryptionInfo encryptionInfo) {
    return serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }
}

0

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

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

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

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

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


นั่นได้พิสูจน์แล้วว่าเป็นอุปสรรคสำคัญสำหรับฉันที่จะเข้าใจการไหล ตัวอย่างนี้มีขนาดค่อนข้างเล็ก แต่อีกตัวอย่างหนึ่งใช้ตัวแปรอินสแตนซ์ 35 (!) สำหรับผลลัพธ์ระดับกลางโดยไม่นับตัวแปรอินสแตนซ์โหลที่มีการอ้างอิง มันค่อนข้างยากที่จะติดตามตามที่คุณต้องติดตามสิ่งที่ได้ตั้งไว้ก่อนหน้านี้แล้ว บางคนถูกนำมาใช้ซ้ำในภายหลังทำให้ยากยิ่งขึ้น โชคดีที่ข้อโต้แย้งที่นำเสนอในที่นี้ทำให้เพื่อนร่วมงานของฉันเชื่อในที่สุดว่าจะเห็นด้วยกับการปรับโครงสร้างใหม่
Alexander
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.