ตัวแปรสถานะเป็นความชั่วร้ายหรือไม่? [ปิด]


47

ตัวแปรสถานะเป็นสิ่งชั่วร้ายหรือไม่? ตัวแปรต่อไปนี้มีความผิดศีลธรรมอย่างลึกซึ้งและเป็นสิ่งที่ชั่วร้ายหรือไม่ที่จะใช้มัน

"ตัวแปรบูลีนหรือเลขจำนวนเต็มที่คุณกำหนดค่าในบางสถานที่จากนั้นลงมาด้านล่างคุณตรวจสอบแล้วใน orther ที่จะทำอะไรหรือไม่เช่นเช่นใช้newItem = trueแล้วบางบรรทัดด้านล่างif (newItem ) then"


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

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


9
ดูเหมือนว่า MainMa และฉันจะมีคำจำกัดความที่แตกต่างกันของ "flag" ฉันกำลังคิดโปรเซสเซอร์ #ifdef คุณถามเรื่องไหน
Karl Bielefeldt

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

บูลีนเป็นธง (จำนวนเต็มดังนั้น ... )
Thomas Eding

7
@KarlBielefeldt ฉันเชื่อว่า OP หมายถึงตัวแปรบูลีนหรือจำนวนเต็มที่คุณกำหนดค่าในบางสถานที่จากนั้นลงด้านล่างคุณตรวจสอบแล้วใน orther ที่จะทำอะไรหรือไม่เช่นเช่นใช้newItem = trueแล้วบางบรรทัดด้านล่างif (newItem ) then
Tulains Córdova

1
พิจารณาการแนะนำการอธิบายการรีแฟคเตอร์ตัวแปรในบริบทนี้ด้วย ตราบใดที่วิธีการยังคงสั้นและมีจำนวนเส้นทางที่น้อยฉันก็ถือว่านี่เป็นการใช้ที่ถูกต้อง
Daniel B

คำตอบ:


41

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

bool capturing, processing, sending;

ทั้งสามสร้างแปดรัฐ (อันที่จริงมีธงอีกสองแห่งเช่นกัน) ไม่ใช่การรวมกันของค่าที่เป็นไปได้ทั้งหมดโดยรหัสและผู้ใช้เห็นข้อบกพร่อง:

if(capturing && sending){ // we must be processing as well
...
}

มันกลับกลายเป็นว่ามีสถานการณ์ที่ข้อสันนิษฐานในคำสั่ง if ด้านบนเป็นเท็จ

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


3
+1 "ควรหลีกเลี่ยง" ฉันจะเพิ่มบางอย่างเกี่ยวกับ 'แต่ธงมีความจำเป็นในบางสถานการณ์' (บางคนอาจพูดว่า 'ความชั่วร้ายที่จำเป็น')
Trevor Boyd Smith

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

ในการสอบของคุณมันควรจะเป็น enum เดียวที่เป็นตัวแทนของรัฐไม่ใช่ 3 บูลีน
949300

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

38

นี่คือตัวอย่างเมื่อธงมีประโยชน์

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

ด้วยแฟล็กการเรียกใช้เมธอดนี้เป็นเรื่องง่าย:

var password = this.PasswordGenerator.Generate(
    CharacterSet.Digits | CharacterSet.LowercaseLetters | CharacterSet.UppercaseLetters);

และยังสามารถลดความซับซ้อนไปที่:

var password = this.PasswordGenerator.Generate(CharacterSet.LettersAndDigits);

หากไม่มีแฟล็กอะไรจะเป็นลายเซ็นของวิธีการ

public byte[] Generate(
    bool uppercaseLetters, bool lowercaseLetters, bool digits, bool basicSymbols,
    bool extendedSymbols, bool greekLetters, bool cyrillicLetters, bool unicode);

เรียกเช่นนี้:

// Very readable, isn't it?
// Tell me just by looking at this code what symbols do I want to be included?
var password = this.PasswordGenerator.Generate(
    true, true, true, false, false, false, false, false);

ตามที่ระบุไว้ในความคิดเห็นวิธีการอื่นจะใช้คอลเลกชัน:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LowercaseLetters,
        CharacterSet.UppercaseLetters,
    });

สิ่งนี้สามารถอ่านได้มากขึ้นเมื่อเทียบกับชุดtrueและfalseแต่ยังมีข้อเสียสองประการ:

ข้อเสียเปรียบที่สำคัญคือเพื่อที่จะอนุญาตให้มีค่ารวมกันเช่นCharacterSet.LettersAndDigitsคุณจะต้องเขียนบางอย่างเช่นในGenerate()วิธีการ:

if (set.Contains(CharacterSet.LowercaseLetters) ||
    set.Contains(CharacterSet.Letters) ||
    set.Contains(CharacterSet.LettersAndDigits) ||
    set.Contains(CharacterSet.Default) ||
    set.Contains(CharacterSet.All))
{
    // The password should contain lowercase letters.
}

อาจเขียนใหม่เช่นนี้:

var lowercaseGroups = new []
{
    CharacterSet.LowercaseLetters,
    CharacterSet.Letters,
    CharacterSet.LettersAndDigits,
    CharacterSet.Default,
    CharacterSet.All,
};

if (lowercaseGroups.Any(s => set.Contains(s)))
{
    // The password should contain lowercase letters.
}

เปรียบเทียบสิ่งนี้กับสิ่งที่คุณมีโดยใช้ธง:

if (set & CharacterSet.LowercaseLetters == CharacterSet.LowercaseLetters)
{
    // The password should contain lowercase letters.
}

ข้อเสียเปรียบเล็กน้อยประการที่สองก็คือมันไม่ชัดเจนว่าวิธีการทำงานถ้าเรียกเช่นนี้:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LettersAndDigits, // So digits are requested two times.
    });

10
ฉันเชื่อว่า OP หมายถึงตัวแปรบูลีนหรือจำนวนเต็มที่คุณกำหนดค่าในบางสถานที่จากนั้นลงมาด้านล่างคุณตรวจสอบแล้วใน orther ที่จะทำอะไรหรือไม่เช่นเช่นใช้newItem = trueแล้วบางบรรทัดด้านล่างif (newItem ) then
Tulains Córdova

1
@MainMa เห็นได้ชัดว่ามี 3: รุ่นที่มีข้อโต้แย้งบูลีน 8 เป็นสิ่งที่ฉันคิดว่าเมื่อฉันอ่าน "ธง" ...
Izkata

4
ขออภัย IMHO เป็นกรณีที่สมบูรณ์แบบสำหรับวิธีการผูกมัด ( en.wikipedia.org/wiki/Method_chaining ) นอกจากนี้คุณสามารถใช้อาร์เรย์พารามิเตอร์ (ต้องเป็นอาเรย์แบบเชื่อมโยงหรือแผนที่) ซึ่งรายการใด ๆ ในอาร์เรย์พารามิเตอร์นั้น คุณไม่ใช้การทำงานของค่าเริ่มต้นสำหรับพารามิเตอร์นั้น ในที่สุดการโทรผ่านการผูกมัดด้วยวิธีการหรืออาร์เรย์พารามิเตอร์สามารถรวบรัดและแสดงผลเป็นบิตแฟล็กเช่นกันไม่ใช่ทุกภาษามีตัวดำเนินการบิต (จริง ๆ แล้วฉันชอบแฟล็กไบนารี แต่จะใช้วิธีที่ฉันเพิ่งพูดถึงแทน)
dukeofgaming

3
มันไม่ OOP มากใช่ไหม ฉันจะสร้างอินเทอร์เฟซ Ala: String myNewPassword = makePassword (randomComposeSupplier (ใหม่ RandomLowerCaseSupplier (), RandomUpperCaseSupplier ใหม่) (), RandomNumberSupplier ใหม่)); ด้วย String makePassword (ซัพพลายเออร์ <ตัวอักษร> charSupplier); และซัพพลายเออร์ <ตัวอักษร> randomComposeSupplier (ซัพพลายเออร์ <ตัวอักษร> ซัพพลายเออร์ ... ); ตอนนี้คุณสามารถใช้ซัพพลายเออร์ของคุณสำหรับงานอื่น ๆ เขียนพวกเขาในวิธีที่คุณชอบและลดความซับซ้อนของวิธีการ generatePassword ของคุณเพื่อให้มันใช้สถานะน้อยที่สุด
Dibbeke

4
@Dibbeke พูดคุยเกี่ยวกับอาณาจักรแห่งคำนาม ...
ฟิล

15

ฟังก์ชั่นบล็อกขนาดใหญ่คือกลิ่นไม่ใช่ธง หากคุณตั้งค่าสถานะในบรรทัดที่ 5 ให้ตรวจสอบเฉพาะค่าสถานะที่บรรทัด 354 เท่านั้นนั่นถือว่าไม่ดี หากคุณตั้งค่าสถานะในบรรทัดที่ 8 และตรวจสอบการตั้งค่าสถานะในบรรทัดที่ 10 ก็ไม่เป็นไร นอกจากนี้หนึ่งหรือสองธงต่อบล็อกของรหัสดี 300 ธงในฟังก์ชั่นไม่ดี


10

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

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


6

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

พิจารณาของ Java Pattern.compile (String regex ธง int)โทร นี่เป็น bitmask แบบดั้งเดิมและใช้งานได้ ดูที่ค่าคงที่ใน java และทุกที่ที่คุณเห็นกลุ่ม 2 nคุณรู้ว่ามีธงอยู่ที่นั่น

ในโลกที่เหมาะสำหรับ refactored เราจะใช้EnumSetแทนซึ่งค่าคงที่จะเป็นค่าใน enum แทนและเป็นเอกสารที่อ่าน:

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

Pattern.compile(String regex, EnumSet<PatternFlagEnum> flags)ในโลกที่สมบูรณ์แบบที่จะกลายเป็นโทร Pattern.compile

ทั้งหมดที่กล่าวว่ายังคงธง มันง่ายกว่าที่จะทำงานด้วยPattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE)มากกว่าที่จะเป็นPattern.compile("foo", new PatternFlags().caseInsenstive().multiline())หรือมีรูปแบบอื่น ๆ ของการพยายามทำในสิ่งที่ธงเป็นจริงและดีสำหรับ

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

ปัญหาเกิดขึ้นเมื่อผู้คนใช้ธงในทางที่ผิดพวกเขาร่วมกันและสร้างชุดธงแฟรงเกนแฟลกทุกประเภทของธงที่ไม่เกี่ยวข้องหรือพยายามที่จะใช้พวกเขาในที่ที่พวกเขาไม่ได้ธงเลย


5

ฉันสมมติว่าเรากำลังพูดถึงธงภายในลายเซ็นวิธี

การใช้การตั้งค่าสถานะเดียวไม่ดีพอ

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

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

if (flag)
   DoFlagSet();
else
   DoFlagNotSet();

นั่นเป็นการแยกความกังวลที่ไม่ดีและโดยปกติคุณสามารถหาทางแก้ไขได้

ปกติฉันมีสองวิธีแยกกัน:

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

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

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


3

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

ปรับปรุง:

ตัวแปรสถานะและ temp เมื่อแสดงสถานะควรจะ refactored วิธีการสอบถาม ค่าสถานะ (booleans, ints, และ primatives อื่น ๆ ) ควรถูกซ่อนอยู่เกือบตลอดเวลาซึ่งเป็นส่วนหนึ่งของรายละเอียดการปฏิบัติ

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


2

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

ธงสามารถใช้งานได้ดีถ้า

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

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

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


1

ฉันสันนิษฐานจากคำถามที่ว่า QA หมายถึงตัวแปร flag (global) และไม่ใช่บิตของพารามิเตอร์ฟังก์ชัน

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


0

ฉันไม่คิดว่าอะไรจะเป็นความชั่วร้ายอย่างแท้จริงในการเขียนโปรแกรม

มีสถานการณ์อื่นที่ธงอาจอยู่ในลำดับซึ่งยังไม่ได้กล่าวถึงที่นี่ ...

พิจารณาการใช้การปิดในตัวอย่างข้อมูล Javascript นี้:

exports.isPostDraft = function ( post, draftTag ) {
  var isDraft = false;
  if (post.tags)
    post.tags.forEach(function(tag){ 
      if (tag === draftTag) isDraft = true;
    });
  return isDraft;
}

ฟังก์ชันภายในที่ถูกส่งผ่านไปยัง "Array.forEach" ไม่สามารถเพียงแค่ "คืนค่าจริง"

ดังนั้นคุณต้องรักษาสถานะไว้ภายนอกด้วยธง

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