หลีกเลี่ยงวิธีที่ซับซ้อนเกินไป - Cyclomatic Complexity


23

ไม่แน่ใจว่าจะไปเกี่ยวกับวิธีการนี้เพื่อลดความซับซ้อนของวัฏจักร โซนาร์รายงาน 13 ขณะที่ 10 คาดว่าจะเป็น ฉันแน่ใจว่าไม่มีอะไรเป็นอันตรายต่อการออกจากวิธีการนี้เหมือนเดิมอย่างไรก็ตามเพียงแค่ท้าทายฉันว่าจะทำตามกฎของ Sonar อย่างไร ความคิดใด ๆ ที่จะได้รับการชื่นชมอย่างมาก

 public static long parseTimeValue(String sValue) {

    if (sValue == null) {
        return 0;
    }

    try {
        long millis;
        if (sValue.endsWith("S")) {
            millis = new ExtractSecond(sValue).invoke();
        } else if (sValue.endsWith("ms")) {
            millis = new ExtractMillisecond(sValue).invoke();
        } else if (sValue.endsWith("s")) {
            millis = new ExtractInSecond(sValue).invoke();
        } else if (sValue.endsWith("m")) {
            millis = new ExtractInMinute(sValue).invoke();
        } else if (sValue.endsWith("H") || sValue.endsWith("h")) {
            millis = new ExtractHour(sValue).invoke();
        } else if (sValue.endsWith("d")) {
            millis = new ExtractDay(sValue).invoke();
        } else if (sValue.endsWith("w")) {
            millis = new ExtractWeek(sValue).invoke();
        } else {
            millis = Long.parseLong(sValue);
        }

        return millis;

    } catch (NumberFormatException e) {
        LOGGER.warn("Number format exception", e);
    }

    return 0;
}

วิธีการทั้งหมดของ ExtractXXX ถูกกำหนดให้เป็นstaticคลาสภายใน ตัวอย่างเช่นหนึ่งด้านล่าง -

    private static class ExtractHour {
      private String sValue;

      public ExtractHour(String sValue) {
         this.sValue = sValue;
      }

      public long invoke() {
         long millis;
         millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 60 * 60 * 1000);
         return millis;
     }
 }

อัพเดท 1

ฉันกำลังจะปักหลักด้วยคำแนะนำที่หลากหลายที่นี่เพื่อตอบสนองผู้ชาย Sonar ห้องพักที่แน่นอนสำหรับการปรับปรุงและลดความซับซ้อน

ฝรั่งFunctionเป็นเพียงพิธีที่ไม่พึงประสงค์ที่นี่ ต้องการอัปเดตคำถามเกี่ยวกับสถานะปัจจุบัน ไม่มีอะไรจะสิ้นสุดที่นี่ โปรดคิด ..

public class DurationParse {

private static final Logger LOGGER = LoggerFactory.getLogger(DurationParse.class);
private static final Map<String, Function<String, Long>> MULTIPLIERS;
private static final Pattern STRING_REGEX = Pattern.compile("^(\\d+)\\s*(\\w+)");

static {

    MULTIPLIERS = new HashMap<>(7);

    MULTIPLIERS.put("S", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractSecond(input).invoke();
        }
    });

    MULTIPLIERS.put("s", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractInSecond(input).invoke();
        }
    });

    MULTIPLIERS.put("ms", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractMillisecond(input).invoke();
        }
    });

    MULTIPLIERS.put("m", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractInMinute(input).invoke();
        }
    });

    MULTIPLIERS.put("H", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractHour(input).invoke();
        }
    });

    MULTIPLIERS.put("d", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractDay(input).invoke();
        }
    });

    MULTIPLIERS.put("w", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractWeek(input).invoke();
        }
    });

}

public static long parseTimeValue(String sValue) {

    if (isNullOrEmpty(sValue)) {
        return 0;
    }

    Matcher matcher = STRING_REGEX.matcher(sValue.trim());

    if (!matcher.matches()) {
        LOGGER.warn(String.format("%s is invalid duration, assuming 0ms", sValue));
        return 0;
    }

    if (MULTIPLIERS.get(matcher.group(2)) == null) {
        LOGGER.warn(String.format("%s is invalid configuration, assuming 0ms", sValue));
        return 0;
    }

    return MULTIPLIERS.get(matcher.group(2)).apply(matcher.group(1));
}

private static class ExtractSecond {
    private String sValue;

    public ExtractSecond(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = Long.parseLong(sValue);
        return millis;
    }
}

private static class ExtractMillisecond {
    private String sValue;

    public ExtractMillisecond(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue));
        return millis;
    }
}

private static class ExtractInSecond {
    private String sValue;

    public ExtractInSecond(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 1000);
        return millis;
    }
}

private static class ExtractInMinute {
    private String sValue;

    public ExtractInMinute(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 60 * 1000);
        return millis;
    }
}

private static class ExtractHour {
    private String sValue;

    public ExtractHour(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 60 * 60 * 1000);
        return millis;
    }
}

private static class ExtractDay {
    private String sValue;

    public ExtractDay(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 24 * 60 * 60 * 1000);
        return millis;
    }
}

private static class ExtractWeek {
    private String sValue;

    public ExtractWeek(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 7 * 24 * 60 * 60 * 1000);
        return millis;
    }
}

}


อัพเดท 2

แม้ว่าฉันจะเพิ่มการอัปเดตของฉันมันก็คุ้มค่ากับเวลามาก ฉันจะเดินหน้าต่อไปเนื่องจากโซนาร์ตอนนี้ไม่บ่น ไม่ต้องกังวลมากและฉันยอมรับคำตอบ mattnz เพราะเป็นวิธีที่จะไปและไม่ต้องการตั้งค่าตัวอย่างที่ไม่ดีสำหรับผู้ที่กระแทกกับคำถามนี้ Bottom line - อย่าผ่านวิศวกรเพื่อเห็นแก่ Sonar (หรือ Half Baked Project Manager) บ่นเกี่ยวกับ CC เพียงแค่ทำสิ่งที่คุ้มค่าเงินสำหรับโครงการ ขอบคุณทุกคน.


4
คำตอบ OO ที่ง่ายที่สุด: private static Dictionary<string,Func<string,long>> _mappingStringToParser;ฉันจะปล่อยให้ส่วนที่เหลือเป็นแบบฝึกหัดให้คุณ (หรือคนที่มีเวลาว่างมากกว่าฉัน) มี API ทำความสะอาดจะพบว่าถ้าคุณมีความคุ้นเคยกับ parsers เอกใด ๆ แต่ฉันจะไม่ไปที่นั่นในขณะนี้ ..
จิมมี่ฮอฟฟา

จะรักถ้าคุณสามารถสำรองบางครั้งใน "monadic parsers" และวิธีที่มันสามารถนำไปใช้กับฟังก์ชั่นขนาดเล็กสวยเช่นนี้ และรหัสนี้มาจาก Java
asyncwait

ExtractBlahคลาสมีการกำหนดอย่างไร? เหล่านี้มาจากห้องสมุดหรือโฮมบรูว์บางอย่าง
ริ้น

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

2
Re Update: ฉันเป็นคนเดียวที่มี "Over Engineered" ดังก้องอยู่ในหัวของฉันหรือไม่?
mattnz

คำตอบ:


46

คำตอบด้านวิศวกรรมซอฟต์แวร์:

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

Minion ทำงานให้กับ บริษัท ข้ามชาติขนาดใหญ่คำตอบ:

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


14
+1 สำหรับการเคารพเหตุผลของการปกครองไม่ใช่กฎเอง
Radu Murzea

5
พูดได้ดี! อย่างไรก็ตามในกรณีอื่น ๆ ที่คุณมี CC = 13 ที่มีหลายระดับซ้อนกันและตรรกะ (แยก) ที่ซับซ้อนมันจะดีกว่าอย่างน้อยพยายามลดความซับซ้อน
grizwako

16

ขอบคุณ @JimmyHoffa, @MichaelT และ @ GlenH7 สำหรับความช่วยเหลือของพวกเขา!

หลาม

สิ่งแรกสิ่งแรกคุณควรยอมรับเฉพาะคำนำหน้าที่รู้จักเท่านั้นนั่นคือ 'H' หรือ 'h' หากคุณต้องยอมรับทั้งสองอย่างคุณควรดำเนินการบางอย่างเพื่อให้สอดคล้องกับการประหยัดพื้นที่ในแผนที่ของคุณ

ในไพ ธ อนคุณสามารถสร้างพจนานุกรมได้

EXTRACTION_MAP = {
    'S': ExtractSecond,
    'ms': ExtractMillisecond,
    'm': ExtractMinute,
    'H': ExtractHour,
    'd': ExtractDay,
    'w': ExtractWeek
}

จากนั้นเราต้องการวิธีใช้สิ่งนี้:

def parseTimeValue(sValue)
    ending = ''.join([i for i in sValue if not i.isdigit()])
    return EXTRACTION_MAP[ending](sValue).invoke()

ควรมีความซับซ้อนของวงจรที่ดีขึ้น


ชวา

เราต้องการตัวคูณแต่ละตัว (1) ตัวเท่านั้น ให้วางไว้ในแผนที่ตามคำตอบอื่น ๆ ที่แนะนำ

Map<String, Float> multipliers = new HashMap<String, Float>();
    map.put("S", 60 * 60);
    map.put("ms", 60 * 60 * 1000);
    map.put("m", 60);
    map.put("H", 1);
    map.put("d", 1.0 / 24);
    map.put("w", 1.0 / (24 * 7));

จากนั้นเราสามารถใช้แผนที่เพื่อจับตัวแปลงที่ถูกต้อง

Pattern foo = Pattern.compile(".*(\\d+)\\s*(\\w+)");
Matcher bar = foo.matcher(sValue);
if(bar.matches()) {
    return (long) (Double.parseDouble(bar.group(1)) * multipliers.get(bar.group(2);
}

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

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

ไม่สามารถพิจารณาคำตอบเหล่านี้ได้ทั้งในการจัดหาโซลูชันเดียวกับโปรแกรมดั้งเดิมซึ่งขึ้นอยู่กับสมมติฐานที่อาจไม่ถูกต้อง รหัส Java: คุณแน่ใจได้อย่างไรว่าวิธีเดียวที่ทำได้คือการใช้ตัวคูณ รหัสไพ ธ อน: คุณแน่ใจได้อย่างไรว่าสตริงไม่ได้รับอนุญาตให้มีอักขระนำหน้า (หรือจุดกลาง) นอกเหนือจากตัวเลข (เช่น "123.456s")
mattnz

@mattnz - ลองดูชื่อตัวแปรภายในตัวอย่างที่มีให้ เป็นที่ชัดเจนว่า OP ได้รับหน่วยเวลาเป็นสตริงแล้วต้องเปลี่ยนเป็นหน่วยเวลาประเภทอื่น ตัวอย่างที่ให้ไว้ในคำตอบนี้เกี่ยวข้องโดยตรงกับโดเมนของ OP คำตอบยังคงให้วิธีการทั่วไปที่สามารถใช้สำหรับโดเมนอื่นได้ คำตอบนี้แก้ปัญหาที่นำเสนอไม่ใช่ปัญหาที่อาจมีการนำเสนอ

5
@mattnz - 1) OP ไม่เคยระบุว่าในตัวอย่างของพวกเขาและอาจไม่สนใจ คุณจะรู้ได้อย่างไรว่าสมมติฐานไม่ถูกต้อง? 2) วิธีการทั่วไปยังคงใช้ได้อาจต้องใช้ regex ที่ซับซ้อนกว่า 3) จุดประสงค์ของคำตอบคือให้เส้นทางที่เป็นแนวคิดในการแก้ไขความซับซ้อนของวงจรและไม่จำเป็นต้องเป็นคำตอบเฉพาะที่รวบรวมได้ 4) ในขณะที่คำตอบนี้ไม่สนใจภาพรวมที่กว้างขึ้นของ "ทำเรื่องซับซ้อน" แต่มันตอบทางอ้อมโดยการแสดงรูปแบบทางเลือกสำหรับรหัส

3

เนื่องจากคุณreturn millisอยู่ในตอนท้ายของifelseifelseอันยิ่งใหญ่สิ่งแรกที่อยู่ในใจคือการคืนค่าทันทีจากภายใน if-block วิธีการนี้ต่อไปนี้อย่างใดอย่างหนึ่งที่แสดงอยู่ในแคตตาล็อกของ refactoring รูปแบบเป็นแทนที่ซ้อนกับเงื่อนไขข้อระวัง

วิธีการมีพฤติกรรมตามเงื่อนไขที่ไม่ชัดเจนว่าเส้นทางการดำเนินการปกติคืออะไร

ใช้ Guard Clauses สำหรับทุกกรณีพิเศษ

มันจะช่วยให้คุณกำจัดสิ่งอื่น ๆ , ทำให้รหัสเรียบและทำให้ Sonar มีความสุข:

    if (sValue.endsWith("S")) {
        return new ExtractSecond(sValue).invoke();
    } // no need in else after return, code flattened

    if (sValue.endsWith("ms")) {
        return new ExtractMillisecond(sValue).invoke();
    }

    // and so on...
    return Long.parseLong(sValue); // forget millis, these aren't needed anymore

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

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


ตัวอย่างโค้ดของคุณสำหรับตัวอย่าง ExtractHour ทำให้ฉันรู้สึกว่าฟังก์ชั่น ExtractXXX ได้รับการออกแบบมาไกลพอสมควร ฉันเดิมพันทุกชั้นเรียนที่เหลืออยู่ลอยๆซ้ำเดียวกันparseDoubleและsubstringและสิ่งคูณเช่น 60 1000 ซ้ำแล้วซ้ำแล้วซ้ำอีก

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

private static class ParseHelper {
    // three things you need to know to parse:
    final String source;
    final int charsToCutAtEnd;
    final long multiplier;

    ParseHelper(String source, int charsToCutAtEnd, long multiplier) {
        this.source = source == null ? "0" : source; // let's handle null here
        this.charsToCutAtEnd = charsToCutAtEnd;
        this.multiplier = multiplier;
    }

    long invoke() {
        // NOTE consider Long.parseLong instead of Double.parseDouble here
        return (long) (Double.parseDouble(cutAtEnd()) * multiplier);
    }

    private String cutAtEnd() {
        if (charsToCutAtEnd == 0) {
            return source;
        }
        // write code that cuts 'charsToCutAtEnd' from the end of the 'source'
        throw new UnsupportedOperationException();
    }
}

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

private ParseHelper setupIfInSecond(ParseHelper original) {
    final String sValue = original.source;
    return sValue.endsWith("s") && !sValue.endsWith("ms")
            ? new ParseHelper(sValue, 1, 1000)
            :  original; // bypass
}

private ParseHelper setupIfMillisecond(ParseHelper original) {
    final String sValue = original.source;
    return sValue.endsWith("ms")
            ? new ParseHelper(sValue, 2, 1)
            : original; // bypass
}

// and so on...

อ้างอิงจากหน่วยการสร้างข้างต้นรหัสการโค้ดของวิธีการของคุณอาจมีลักษณะดังต่อไปนี้:

public long parseTimeValue(String sValue) {

   return setupIfSecond(
           setupIfMillisecond(
           setupIfInSecond(
           setupIfInMinute(
           setupIfHour(
           setupIfDay(
           setupIfWeek(
           new ParseHelper(sValue, 0, 1))))))))
           .invoke();
}

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


1

ถ้าคุณต้องการ refactor จริง ๆ คุณสามารถทำสิ่งนี้:

// All of your Extract... classes will have to implement this interface!
public Interface TimeExtractor
{
    public long invoke();
}

private static class ExtractHour implements TimeExtractor
{
  private String sValue;


  /*Not sure what this was for - might not be necessary now
  public ExtractHour(String sValue)
  {
     this.sValue = sValue;
  }*/

  public long invoke(String s)
  {
     this.sValue = s;
     long millis;
     millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 60 * 60 * 1000);
     return millis;
 }
}

private static HashMap<String, TimeExtractor> extractorMap= new HashMap<String, TimeExtractor>();

private void someInitMethod()
{
   ExtractHour eh = new ExtractorHour;
   extractorMap.add("H",eh);
   /*repeat for all extractors */
}

public static long parseTimeValue(String sValue)
{
    if (sValue == null)
    {
        return 0;
    }
    String key = extractKeyFromSValue(sValue);
    long millis;
    TimeExtractor extractor = extractorMap.get(key);
    if (extractor!=null)
    {
      try
      {
         millis= extractor.invoke(sValue);
      }
        catch (NumberFormatException e)
      {
          LOGGER.warn("Number format exception", e);
      }
    }
    else
       LOGGER.error("NO EXTRACTOR FOUND FOR "+key+", with sValue: "+sValue);

    return millis;
}

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

มันค่อนข้างหยาบที่นี่ แต่ฉันหวังว่ามันชัดเจนพอ ฉันไม่ได้กรอกรายละเอียดของextractKeyFromSValue()เพราะฉันแค่ไม่รู้ว่าสายเหล่านี้จะทำอะไรได้อย่างถูกต้อง ดูเหมือนว่าเป็นอักขระที่ไม่ใช่ตัวเลข 1 หรือ 2 ตัวสุดท้าย (regex สามารถแยกได้ง่ายพออาจ.*([a-zA-Z]{1,2})$จะใช้งานได้) แต่ฉันไม่แน่ใจ 100% ...


คำตอบเดิม:

คุณสามารถเปลี่ยน

else if (sValue.endsWith("H") || sValue.endsWith("h")) {

ไปยัง

else if (sValue.toUpper().endsWith("H")) {

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

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


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

@asyncwait: อ่าฉันคิดว่าคุณกำลังทำรายงานนี้จากโซนาร์อย่างจริงจังมากกว่านั้น ใช่การเปลี่ยนแปลงที่ฉันแนะนำจะไม่ทำให้เกิดความแตกต่างอย่างใหญ่หลวง - ฉันไม่คิดว่ามันจะพาคุณจาก 13 ถึง 10 แต่ในเครื่องมือบางอย่างที่ฉันเห็นสิ่งนี้ทำให้เกิดความแตกต่างที่เห็นได้ชัดเจน
FrustratedWithFormsDesigner

เพียงแค่เพิ่มสาขา IF อื่นเพิ่ม CC เป็น +1
asyncwait

0

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

//utility class for matching values
private static class ValueMatchingPredicate implements Predicate<String>{
    private final String[] suffixes;

    public ValueMatchingPredicate(String[] suffixes) {      
        this.suffixes = suffixes;
    }

    public boolean apply(String sValue) {
        if(sValue == null) return false;

        for (String suffix : suffixes) {
            if(sValue.endsWith(suffix)) return true;
        }

        return false;
    }

    public static Predicate<String> withSuffix(String... suffixes){         
        return new ValueMatchingPredicate(suffixes);
    }       
}

//enum containing all possible options
private static enum TimeValueExtractor {                
    SECOND(
        ValueMatchingPredicate.withSuffix("S"), 
        new Function<String, Long>(){ 
            public Long apply(String sValue) {  return new ExtractSecond(sValue).invoke(); }
        }),

    MILISECOND(
        ValueMatchingPredicate.withSuffix("ms"), 
        new Function<String, Long>(){
            public Long apply(String sValue) { return new ExtractMillisecond(sValue).invoke(); }
        }),

    IN_SECOND(
        ValueMatchingPredicate.withSuffix("s"),
        new Function<String, Long>(){
            public Long apply(String sValue) { return new ExtractInSecond(sValue).invoke(); }
        }),

    IN_MINUTE(
        ValueMatchingPredicate.withSuffix("m"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractInMinute(sValue).invoke(); }
        }),

    HOUR(
        ValueMatchingPredicate.withSuffix("H", "h"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractHour(sValue).invoke(); }
        }),

    DAY(
        ValueMatchingPredicate.withSuffix("d"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractDay(sValue).invoke(); }
        }),

    WEEK(
        ValueMatchingPredicate.withSuffix("w"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractWeek(sValue).invoke(); }
        });

    private final Predicate<String>      valueMatchingRule;
    private final Function<String, Long> extractorFunction;

    public static Long DEFAULT_VALUE = 0L;

    private TimeValueExtractor(Predicate<String> valueMatchingRule, Function<String, Long> extractorFunction) {
        this.valueMatchingRule = valueMatchingRule;
        this.extractorFunction = extractorFunction;
    }

    public boolean matchesValueSuffix(String sValue){
        return this.valueMatchingRule.apply(sValue);
    }

    public Long extractTimeValue(String sValue){
        return this.extractorFunction.apply(sValue);
    }

    public static Long extract(String sValue) throws NumberFormatException{
        TimeValueExtractor[] extractors = TimeValueExtractor.values();

        for (TimeValueExtractor timeValueExtractor : extractors) {
            if(timeValueExtractor.matchesValueSuffix(sValue)){
                return timeValueExtractor.extractTimeValue(sValue);
            }
        }

        return DEFAULT_VALUE;
    }
}

//your function
public static long parseTimeValue(String sValue){
    try{
        return TimeValueExtractor.extract(sValue);
    } catch (NumberFormatException e) {
        //LOGGER.warn("Number format exception", e);
        return TimeValueExtractor.DEFAULT_VALUE;
    }
}

0

เกี่ยวข้องกับความคิดเห็นของคุณ:

Bottom line - อย่าผ่านวิศวกรเพื่อเห็นแก่ Sonar (หรือ Half Baked Project Manager) บ่นเกี่ยวกับ CC เพียงแค่ทำสิ่งที่คุ้มค่าเงินสำหรับโครงการ

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

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


0

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

public static long parseTimeValue(String sValue) {

    if (sValue == null) {
        return 0;
    }

    try {
        return getMillis(sValue);
    } catch (NumberFormatException e) {
        LOGGER.warn("Number format exception", e);
    }

    return 0;
}

private static long getMillis(String sValue) {
    if (sValue.endsWith("S")) {
        return new ExtractSecond(sValue).invoke();
    } else if (sValue.endsWith("ms")) {
        return new ExtractMillisecond(sValue).invoke();
    } else if (sValue.endsWith("s")) {
        return new ExtractInSecond(sValue).invoke();
    } else if (sValue.endsWith("m")) {
        return new ExtractInMinute(sValue).invoke();
    } else if (sValue.endsWith("H") || sValue.endsWith("h")) {
        return new ExtractHour(sValue).invoke();
    } else if (sValue.endsWith("d")) {
        return new ExtractDay(sValue).invoke();
    } else if (sValue.endsWith("w")) {
        return new ExtractWeek(sValue).invoke();
    } else {
        return Long.parseLong(sValue);
    }
}

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

นอกจากนี้ผู้อ่าน Clean Code อาจสนุกกับความจริงที่ว่าวิธีการระดับบนสุดในตอนนี้นั้นง่ายและสั้น

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