अपवाद या अतिरेक के बिना इनपुट सत्यापन कैसे करें


11

जब मैं एक विशिष्ट कार्यक्रम के लिए एक इंटरफ़ेस बनाने की कोशिश कर रहा हूं तो मैं आमतौर पर अपवादों को फेंकने से बचने की कोशिश कर रहा हूं जो गैर-मान्य इनपुट पर निर्भर करता है।

तो अक्सर ऐसा होता है कि मैंने इस तरह के कोड का एक टुकड़ा सोचा है (यह एक उदाहरण के लिए सिर्फ एक उदाहरण है, यह जो कार्य करता है उसे ध्यान में न रखें, जावा में उदाहरण है):

public static String padToEvenOriginal(int evenSize, String string) {
    if (evenSize % 2 == 1) {
        throw new IllegalArgumentException("evenSize argument is not even");
    }

    if (string.length() >= evenSize) {
        return string;
    }

    StringBuilder sb = new StringBuilder(evenSize);
    sb.append(string);
    for (int i = string.length(); i < evenSize; i++) {
        sb.append(' ');
    }
    return sb.toString();
}

ठीक है, इसलिए कहते हैं कि evenSizeवास्तव में उपयोगकर्ता इनपुट से लिया गया है। तो मुझे यकीन नहीं है कि यह भी है लेकिन मैं इस पद्धति को इस संभावना के साथ नहीं बुलाना चाहता कि कोई अपवाद फेंका जाए। इसलिए मैं निम्नलिखित कार्य करता हूं:

public static boolean isEven(int evenSize) {
    return evenSize % 2 == 0;
}

लेकिन अब मुझे दो चेक मिले हैं जो समान इनपुट सत्यापन करते हैं: ifकथन में अभिव्यक्ति और स्पष्ट चेक इन isEven। डुप्लिकेट कोड, अच्छा नहीं है, तो आइए रिफ्लेक्टर:

public static String padToEvenWithIsEven(int evenSize, String string) {
    if (!isEven(evenSize)) { // to avoid duplicate code
        throw new IllegalArgumentException("evenSize argument is not even");
    }

    if (string.length() >= evenSize) {
        return string;
    }

    StringBuilder sb = new StringBuilder(evenSize);
    sb.append(string);
    for (int i = string.length(); i < evenSize; i++) {
        sb.append(' ');
    }
    return sb.toString();
}

ठीक है, कि इसे हल कर दिया, लेकिन अब हम निम्नलिखित स्थिति में आते हैं:

String test = "123";
int size;
do {
    size = getSizeFromInput();
} while (!isEven(size)); // checks if it is even
String evenTest = padToEvenWithIsEven(size, test);
System.out.println(evenTest); // checks if it is even (redundant)

अब हमें एक निरर्थक जांच मिल गई है: हम पहले से ही जानते हैं कि मूल्य सम है, लेकिन फिर padToEvenWithIsEvenभी पैरामीटर चेक करता है, जो हमेशा सही रहेगा, जैसा कि हमने पहले ही इस फ़ंक्शन को कहा था।

अब isEvenबेशक इसके लिए कोई समस्या नहीं है, लेकिन यदि पैरामीटर जाँच अधिक बोझिल है तो इससे बहुत अधिक लागत आ सकती है। इसके अलावा, एक निरर्थक कॉल का प्रदर्शन बस सही नहीं लगता है।

कभी-कभी हम "मान्य प्रकार" की शुरुआत करके या एक फ़ंक्शन बनाकर काम कर सकते हैं जहां यह समस्या नहीं हो सकती है:

public static String padToEvenSmarter(int numberOfBigrams, String string) {
    int size = numberOfBigrams * 2;
    if (string.length() >= size) {
        return string;
    }

    StringBuilder sb = new StringBuilder(size);
    sb.append(string);
    for (int i = string.length(); i < size; i++) {
        sb.append('x');
    }
    return sb.toString();
}

लेकिन इसके लिए कुछ स्मार्ट सोच और काफी बड़े रिफ्लेक्टर की जरूरत होती है।

क्या ऐसा (अधिक) सामान्य तरीका है जिसमें हम isEvenदोहरा पैरामीटर जाँच करने और करने के लिए अनावश्यक कॉल से बच सकते हैं ? मैं चाहता हूं कि समाधान वास्तव padToEvenमें एक अवैध पैरामीटर के साथ कॉल न करे , अपवाद को ट्रिगर करता है।


बिना किसी अपवाद के मुझे अपवाद-मुक्त प्रोग्रामिंग से मतलब नहीं है, मेरा मतलब है कि उपयोगकर्ता इनपुट डिज़ाइन द्वारा अपवाद को ट्रिगर नहीं करता है, जबकि सामान्य फ़ंक्शन में अभी भी पैरामीटर चेक होता है (यदि बस प्रोग्रामिंग त्रुटियों से बचाने के लिए)।


क्या आप केवल अपवाद को हटाने की कोशिश कर रहे हैं? आप वास्तविक आकार क्या होना चाहिए, इस बारे में एक धारणा बनाकर कर सकते हैं। उदाहरण के लिए, यदि आप 13 में पास होते हैं, तो 12 या 14 में पैड करें और पूरी तरह से चेक से बचें। यदि आप उन मान्यताओं में से एक नहीं बना सकते हैं, तो आप अपवाद के साथ फंस गए हैं क्योंकि पैरामीटर अनुपयोगी है और आपका कार्य जारी नहीं रह सकता है।
रॉबर्ट हार्वे

@RobertHarvey वह - मेरी राय में - कम से कम आश्चर्य के सिद्धांत के साथ-साथ तेजी से विफल होने के सिद्धांत के खिलाफ सीधे जाता है। यदि इनपुट पैरामीटर शून्य है (तो फिर परिणाम को सही ढंग से हैंडल करना भूल जाता है, तो निश्चित रूप से यह बहुत अच्छा है।
मार्टन बोदवेस

एर्गो, अपवाद। सही?
रॉबर्ट हार्वे

2
रुको क्या? आप यहाँ सलाह के लिए आए थे, है ना? मैं जो कह रहा हूं (मेरे स्पष्ट रूप से इतने सूक्ष्म तरीके से नहीं), वह यह है कि वे अपवाद डिजाइन द्वारा हैं , इसलिए यदि आप उन्हें खत्म करना चाहते हैं, तो आपको शायद एक अच्छा कारण होना चाहिए। वैसे, मैं अमोन से सहमत हूं: आपके पास शायद ऐसा फ़ंक्शन नहीं होना चाहिए जो किसी पैरामीटर के लिए विषम संख्या को स्वीकार नहीं कर सकता है।
रॉबर्ट हार्वे

1
@MaartenBodewes: आपको यह याद रखना होगा कि उपयोगकर्ता इनपुट का सत्यापन padToEvenWithIsEven नहीं करता है । यह कॉलिंग में प्रोग्रामिंग त्रुटियों के खिलाफ खुद को बचाने के लिए अपने इनपुट पर एक वैधता जांच करता है । इस सत्यापन को कितना व्यापक होना चाहिए, यह एक लागत / जोखिम विश्लेषण पर निर्भर करता है, जहां आप चेक की लागत उस जोखिम के विरुद्ध डालते हैं जिसे कॉलिंग कोड लिखने वाला व्यक्ति गलत पैरामीटर में पास करता है।
बार्ट वैन इनगेन शेनौ

जवाबों:


7

अपने उदाहरण के मामले में, सबसे अच्छा समाधान अधिक सामान्य पैडिंग फ़ंक्शन का उपयोग करना है; यदि कॉलर एक समान आकार में पैड करना चाहता है, तो वे खुद को जांच सकते हैं।

public static String padString(int size, String string) {
    if (string.length() >= size) {
        return string;
    }

    StringBuilder sb = new StringBuilder(size);
    sb.append(string);
    for (int i = string.length(); i < size; i++) {
        sb.append(' ');
    }
    return sb.toString();
}

यदि आप एक मूल्य पर बार-बार एक ही सत्यापन करते हैं, या केवल एक प्रकार के मूल्यों के सबसेट को अनुमति देना चाहते हैं, तो माइक्रोटेप्स / छोटे प्रकार सहायक हो सकते हैं। सामान्य प्रयोजन की उपयोगिताओं के लिए जैसे पेडिंग यह एक अच्छा विचार नहीं है, लेकिन यदि आपका मूल्य आपके डोमेन मॉडल में कुछ विशेष भूमिका निभाता है, तो आदिम मूल्यों के बजाय एक समर्पित प्रकार का उपयोग करना एक महान कदम हो सकता है। यहाँ, आप परिभाषित कर सकते हैं:

final class EvenInteger {
  public final int value;

  public EvenInteger(int value) {
    if (!(value % 2 == 0))
      throw new IllegalArgumentException("EvenInteger(" + value + ") is not even");
    this.value = value;
  }
}

अब आप घोषणा कर सकते हैं

public static String padStringToEven(EvenInteger evenSize, String string)
    ...

और किसी भी सत्यापन के अंदर नहीं करना है। इस तरह के एक साधारण परीक्षण के लिए किसी ऑब्जेक्ट के अंदर एक इंट रैपिंग, रन टाइम प्रदर्शन के मामले में अधिक महंगा होगा, लेकिन आपके लाभ के लिए टाइप सिस्टम का उपयोग करने से कीड़े कम हो सकते हैं और आपके डिज़ाइन को स्पष्ट कर सकते हैं।

इस तरह के छोटे प्रकारों का उपयोग करना तब भी उपयोगी हो सकता है, जब वे कोई सत्यापन नहीं करते हैं, उदाहरण के लिए, एक FirstNameसे प्रतिनिधित्व करने वाले एक स्ट्रिंग को अस्वीकार करने के लिए LastName। मैं अक्सर इस पैटर्न का उपयोग सांख्यिकीय रूप से टाइप की जाने वाली भाषाओं में करता हूं।


क्या आपका दूसरा कार्य अभी भी कुछ मामलों में अपवाद नहीं है?
रॉबर्ट हार्वे

1
@RobertHarvey हाँ, उदाहरण के लिए अशक्त बिंदु के मामले में। लेकिन अब मुख्य जांच - कि संख्या सम है - कॉल करने वाले की जिम्मेदारी में कार्य से बाहर कर दिया जाता है। वे तब अपवाद से निपट सकते हैं जिस तरह से वे फिट दिखते हैं। मुझे लगता है कि मान्यता कोड में दोहराव से छुटकारा पाने की तुलना में सभी अपवादों से छुटकारा पाने पर जवाब कम है।
आमोन

1
बहुत बढ़िया जवाब। निश्चित रूप से जावा में डेटा कक्षाएं बनाने के लिए जुर्माना काफी अधिक है (अतिरिक्त कोड का एक बहुत), लेकिन यह विचार की तुलना में भाषा की एक समस्या है जो इसे प्रस्तुत किया गया है। मुझे लगता है कि आप इस समाधान का उपयोग उन सभी मामलों के लिए नहीं करेंगे जहां मेरी समस्या होगी, लेकिन यह आदिम अति प्रयोग के खिलाफ या किनारे के मामलों के लिए एक अच्छा समाधान है जहां पैरामीटर चेक की लागत असाधारण रूप से कठिन है।
Maarten Bodewes

1
@MaartenBodewes यदि आप सत्यापन करना चाहते हैं, तो आपको अपवाद जैसी किसी चीज़ से छुटकारा नहीं मिल सकता है। खैर, विकल्प एक स्थिर कार्य का उपयोग करना है जो एक वैध वस्तु या विफलता पर अशक्त लौटाता है, लेकिन इसका मूल रूप से कोई लाभ नहीं है। लेकिन फ़ंक्शन को कंस्ट्रक्टर में सत्यापन से बाहर ले जाकर, हम अब सत्यापन त्रुटि की संभावना के बिना फ़ंक्शन को कॉल कर सकते हैं। इससे कॉलर को सारा कंट्रोल मिल जाता है। उदाहरण के लिए:EvenInteger size; while (size == null) { try { size = new EvenInteger(getSizeFromInput()); } catch(...){}} String result = padStringToEven(size,...);
आमोन

1
@MaartenBodewes डुप्लीकेट सत्यापन हर समय होता है। उदाहरण के लिए, किसी दरवाजे को बंद करने की कोशिश करने से पहले आपकी जांच हो सकती है कि क्या यह पहले से ही बंद है, लेकिन दरवाजा खुद को भी बंद होने की अनुमति नहीं देगा यदि यह पहले से ही है। इससे बाहर निकलने का कोई रास्ता नहीं है, सिवाय इसके कि आप हमेशा इस बात पर भरोसा करें कि कॉल करने वाला कोई अमान्य कार्य नहीं करता है। आपके मामले में आपके पास एक unsafePadStringToEvenऑपरेशन हो सकता है जो किसी भी चेक का प्रदर्शन नहीं करता है, लेकिन यह सिर्फ सत्यापन से बचने के लिए एक बुरे विचार की तरह लगता है।

9

@ अमोन के जवाब के विस्तार के रूप में, हम उसके EvenIntegerसाथ गठबंधन कर सकते हैं जो कार्यात्मक प्रोग्रामिंग समुदाय "स्मार्ट कंस्ट्रक्टर" कह सकता है - एक फ़ंक्शन जो गूंगा कंस्ट्रक्टर को लपेटता है और सुनिश्चित करता है कि ऑब्जेक्ट एक वैध स्थिति में है (हम गूंगा बनाते हैं कंस्ट्रक्टर वर्ग या गैर-क्लास आधारित भाषाओं के मॉड्यूल / पैकेज निजी में यह सुनिश्चित करने के लिए कि केवल स्मार्ट कंस्ट्रक्टर का उपयोग किया जाता है)। Optionalफ़ंक्शन को अधिक संयोजनीय बनाने के लिए ट्रिक (या समतुल्य) को वापस करना है।

public final class EvenInteger {
    private final int value;

    private EvenInteger(value) {
        this.value = value;
    }

    public static Optional<EvenInteger> of(final int value) {
        if (value % 2 == 0) {
            return Optional.of(new EvenInteger(value));
        }
        return Optional.empty();
    }

    public int getValue() {
        return this.value;
    }
}

फिर हम Optionalइनपुट लॉजिक को लिखने के लिए आसानी से मानक विधियों का उपयोग कर सकते हैं :

class GetEvenInput {
    public Optional<EvenInteger> askOnce() {
        int size = getSizeFromInput();
        return EvenInteger.of(size);
    }

    public EvenInteger keepAsking() {
        return askOnce().orElseGet(() -> keepAsking());
    }
}

आप keepAsking()एक डू-लूप के साथ अधिक मुहावरेदार जावा शैली में भी लिख सकते हैं ।

Optional<EvenInteger> result;
do {
    result = askOnce();
} while (!result.isPresent());

return result.get();

फिर अपने बाकी कोड में आप इस बात पर निर्भर हो सकते हैं EvenIntegerकि यह वास्तव में होगा और हमारी जाँच केवल एक बार ही लिखी गई थी EvenInteger::of


सामान्य रूप से वैकल्पिक संभावित रूप से अमान्य डेटा का अच्छी तरह से प्रतिनिधित्व करता है। यह TryParse विधियों के ढेर सारे के अनुरूप है, जो डेटा और ध्वज दोनों को इनपुट वैधता का संकेत देता है। जटिल समय की जाँच के साथ।
बसिलेव्स

1

यदि सत्यापन का परिणाम समान है और सत्यापन एक ही कक्षा में किया जा रहा है, तो दो बार सत्यापन करना एक समस्या है। वह तुम्हारा उदाहरण नहीं है। आपके रीफैक्चर्ड कोड में, पहला पहला चेक जो किया जाता है वह एक इनपुट वेलिडेशन है, एक नए इनपुट में विफलता के परिणाम का अनुरोध किया जा रहा है। दूसरा चेक पहले से पूरी तरह से स्वतंत्र है, क्योंकि यह एक सार्वजनिक विधि padToEvenWithEven पर है जिसे कक्षा के बाहर से बुलाया जा सकता है और इसका एक अलग परिणाम (अपवाद) होता है।

आपकी समस्या गैर-सूखी के लिए गलती से समान कोड की समस्या के समान है। आप डिजाइन के साथ कार्यान्वयन को भ्रमित कर रहे हैं। वे समान नहीं हैं, और सिर्फ इसलिए कि आपके पास एक या एक दर्जन लाइनें हैं जो समान हैं, इसका मतलब यह नहीं है कि वे एक ही उद्देश्य की सेवा कर रहे हैं और हमेशा के लिए विनिमेय माना जा सकता है। इसके अलावा, शायद आपकी कक्षा बहुत अधिक कर रही है, लेकिन इसे छोड़ दें क्योंकि यह शायद सिर्फ एक खिलौना उदाहरण है ...

यदि यह एक प्रदर्शन समस्या है, तो आप एक निजी विधि बनाकर समस्या का समाधान कर सकते हैं, जो कोई भी सत्यापन नहीं करता है, जिसे आपके सार्वजनिक पैडटाइवेनविथइवन ने सत्यापन करने के बाद कॉल किया था और जिसके बजाय आपका अन्य तरीका कॉल करेगा। यदि यह एक प्रदर्शन समस्या नहीं है, तो अपने विभिन्न तरीकों को उन चेकों को करने दें जो उन्हें अपने असाइन किए गए कार्यों को करने की आवश्यकता है।


ओपी ने कहा कि फंक्शन थ्रो को रोकने के लिए इनपुट वेलिडेशन किया जाता है। तो चेक पूरी तरह से निर्भर और जानबूझकर समान हैं।
D ड्रमर

@Drmmr: नहीं, वे निर्भर नहीं हैं। फ़ंक्शन फेंकता है क्योंकि यह अनुबंध का हिस्सा है। जैसा कि मैंने कहा, इसका उत्तर यह है कि एक निजी पद्धति का निर्माण अपवाद को छोड़कर सब कुछ करता है और फिर सार्वजनिक विधि को निजी विधि कहता है। सार्वजनिक विधि चेक को बरकरार रखती है, जहां यह एक अलग उद्देश्य प्रदान करता है - अनवील्टेड इनपुट को संभालने के लिए।
jmoreno
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.