Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I started reading a Clean Code book and some other articles on the Internet about OOD and design patterns and I'm a little confused. I've started to code a little string to integer parser which currently parses a first integer in string larger than 0 or returns -1.

Could you look and give me some idea if this is written correctly with general rules of good programming? I'm not sure about usage of Index instances in the IntegerSubstringer class.

main method

public static void main (String Args[])
    {
        IntegerParser parser = new IntegerParser();
        System.out.println( parser.getFirstInt("Blaola") );
    }

IntegerParser.java

package stringParser.integerParser;

public class IntegerParser
{
    IntegerSubstringer substringer;

    public IntegerParser()
    {
        substringer = new IntegerSubstringer();
    }

    public int getFirstPositiveInt(String textToParse)
    {
        String numberSubstring = substringer.cutFirstNumber(textToParse);
        if(!numberSubstring.equals(""))
            return parse(numberSubstring);
        return -1;
    }

    private int parse(String substringContainingNumbersOnly)
    {
        return Integer.parseInt(substringContainingNumbersOnly);
    }
}

IntegerSubstringer.java

package stringParser.integerParser;

public class IntegerSubstringer
{
    IntegerFinder finder;
    String text;
    Index startIndex;
    Index endIndex;

    public IntegerSubstringer()
    {
        finder = new IntegerFinder();
    }

    public String cutFirstNumber( String text )
    {
        this.text = text;
        setStartIndexAtTheFirstNumber();
        iterateToTheEndOfTheNumberSeries();
        if (startIndex.isInvalid()) return new String();
        return makeSubstringAccordToIndexes();
    }

    private void iterateToTheEndOfTheNumberSeries()
    {
        setEndIndexAfterStartIndex();
        while( endOfTextNotReached() && finder.letterIsANumber(text, endIndex) )
        {
            endIndex.increaseValue();
        }
        if (endIndex.isInvalid())
            setEndIndexAfterStartIndex();
    }

    private String makeSubstringAccordToIndexes()
    {
        return text.substring(startIndex.getValue(), endIndex.getValue());
    }

    private void setStartIndexAtTheFirstNumber()
    {
        startIndex = finder.findFirstNumber(text);
    }

    private void setEndIndexAfterStartIndex()
    {
        endIndex = new Index(startIndex);
        endIndex.increaseValue();
    }

    private boolean endOfTextNotReached()
    {
        return text.length() > endIndex.getValue();
    }
}

IntegerFinder.java

package stringParser.integerParser;

import stringParser.LetterTypeArbiter;

public class IntegerFinder
{
    private LetterTypeArbiter rater;
    private Index index;

    public IntegerFinder()
    {
        index = new Index();
        rater = new LetterTypeArbiter();
    }

    public Index findFirstNumber ( String text )
    {
        for ( index.resetValue(); index.getValue() < text.length() ; index.increaseValue() )
            if ( stringHasANumberAtIndex(text, index) )
                return index;
        return new InvalidIndex();
    }

    public boolean letterIsANumber ( String text, Index currentIndex )
    {
        index = new Index(currentIndex);

        if ( stringHasANumberAtIndex(text, index) )
                return true;
        return false;
    }

    private boolean stringHasANumberAtIndex ( String text, Index index )
    {
        return rater.obtainsANumber( getLetterFromString(text, index) );
    }

    private int getLetterFromString ( String text, Index index )
    {
        return text.charAt( index.getValue() );
    }
}

Index.java

package stringParser.integerParser;

public class Index
{
    private int value;

    public Index()
    {
        value = 0;
    }

    public Index( Index index )
    {
        value = index.getValue();
    }

    public int getValue()
    {
        return value;
    }

    public void resetValue()
    {
        value = 0;
    }

    public void increaseValue()
    {
        value++;
    }

    public boolean isInvalid()
    {
        return value < 0;
    }
}
share|improve this question
1  
So given an input abc123, it should return 123? This is really quite a complicated solution if that's the case... –  h.j.k. May 4 at 0:52
    
Like many mentioned before me, this seems like an over design. Finding an int in a string can be done in 5-10 lines. You don't need 5 classes for that. Another note, your class and function names are too long. You should try to be more consise. For example makeSubstringAccordToIndexes would be more readable in a form of Substring(start, length) or substringContainingNumbersOnly could be numericString –  Daniel Sokolov May 4 at 2:15
    
This is only one possible functionality of the parser. I wanted to stay open for new functionalities ( like remove first number after specific substring ) in the future. –  hejcz May 4 at 9:10

4 Answers 4

Index = int

My first thought as I skimmed through your code was that you have a whole class that seems to be nothing more than a wrapper for an int. The only method of any significance is isInvalid() which could easily be moved to somewhere else or just inlined when needed. I think it would be best to remove this class and just use ints as indices.

KISS

I didn't read the rest of the code very thoroughly but it seems like a lot of code to solve a simple problem. I prefer the KISS methodology (keep it simple stupid) to complicated solutions. I'm sure if you tried to rewrite your code with simplicity as a goal, you could come up with something much smaller and simpler.

Here's an example of how you could simplify. You have this function:

private int parse(String substringContainingNumbersOnly)
{
    return Integer.parseInt(substringContainingNumbersOnly);
}

Notice that all it does is call another function. You could remove this function and replace the call to it with the call to Integer.parseInt directly. Now someone reading the code will know exactly what your code is doing when they read that line. With your current code, they would have to skip to the definition of parse to figure out what is happening, because the function name "parse" doesn't really explain what it does.

In fact many of your functions are one liners. Instead of having these one line functions which are mostly called from one place only, you could just put the one line in the calling function and add a comment about what is happening (if it isn't clear already). Right now, it almost seems like every line of code is a function call to a new function.

share|improve this answer
    
Ok. So I have few questions to you :) I've read that every primitive type needs to be wrapped. Why then we want to leave a primitive int hanging around? Moreover as I mentioned this isnt the whole parser. I would like to add more functionalities soon. How then should I start thinking about needed classes. Could you pls write a simple basic plan of this kind of parser? People write that it isnt the problem for few classes and I agree. I just wanted to make it open for new functions. –  hejcz May 4 at 8:08
    
@hejcz Who told you to wrap primitive types? That's crazy. If you really need to have an object for a primitive type, use the java provided objects Integer, Long, etc. I don't know what classes you will need without knowing what your parser needs to do. But I would argue to always simplify when possible. Having many classes is not something to be proud of. –  JS1 May 4 at 8:12
    
So maybe i misunderstood something with primitives wrapping. Maybe that was something like instead of having int x,y we should have a Point object with x,y inside. However you say that this is bad to have many classes. Is this true according to Single Responsibility Principle? If class has one responsibility then how could it be big? –  hejcz May 4 at 9:06
1  
@hejcz It isn't necessarily bad to have a lot of classes. But creating classes for no good reason is bad. In your code above, I would have created 1 class only. Later if I added more functionality, I would create more classes and maybe some of the code would move from one class to another. It's hard to determine what classes you will need until you actually try to solve the problem. Other people may have different ways of planning out their program, but I think a lot of time can be wasted making plans that will later change once you start finding out what all the problems really are. –  JS1 May 4 at 9:18
2  
@hejcz From what I could see, the classes seem to be in a chain. The Main class has a reference to the IntegerParser, which has a reference to the IntegerSubstringer, which has a reference to the `IntegerFinder, etc. I wouldn't create that string of classes and would rather do it in one, or maximum two, classes. –  Manny Meng May 4 at 15:06

I have written another parser class doing exactly the same. However I think that it break SRP as it parses, iterates through string and checks if letters are numeric. Could someone involved in OOP say what does he think of it? I agree that wrapping Index in class was a mistake, but it lost modularity in my opinion. I also see duplication in two IntegerParser's methods however I'm not sure how to remove it. In the end I think this is much less open for adding new functionalities.

Main.java

public static void main(String[] Args)
{
        IntegerParser intparser = new IntegerParser();
        System.out.println(intparser.getFirstNumber( "lolo543" ));
}

IntegerParser.java

package intparser;

public class IntegerParser
{
    private String parsingText;
    private int startIndex;
    private int lastIndex;

    public int getFirstNumber(  String textToParse )
    {
        parsingText = textToParse;
        startIndex = getIndexOfFirstNumericChain(0);
        lastIndex = getIndexOfLastNumberInChain(startIndex);
        if ( startIndex == -1 )
            return -1;
        if ( lastIndex == -1 )
            lastIndex = parsingText.length();
        return Integer.parseInt( parsingText.substring(startIndex, lastIndex) );
    }

    private int getIndexOfFirstNumericChain( int index )
    {
        if ( index < 0 ) return -1;
        for ( int i = index; i < parsingText.length() ; i++ )
            if (numberAt(i)) return i;
        return -1;
    }

    private int getIndexOfLastNumberInChain( int index )
    {
        if ( index < 0 ) return -1;
        for ( int i = index; i < parsingText.length() ; i++ )
            if (!numberAt(i)) return i;
        return -1;
    }

    private boolean numberAt( int index )
    {
        char letter = parsingText.charAt(index);
        if (  letter >= '0' && letter <= '9' )
            return true;
        return false;
    }   
}
share|improve this answer
1  
I like this version a lot better! –  JS1 May 4 at 20:06
    
This would probably be better if asked as another question with a link to this original one... :) –  h.j.k. May 5 at 3:18
    
It's a lot simpler in the right direction, but I still think you are breaking down the solution into too many smaller steps. Post this as a new question and I'll try to jump in from here. ;) –  h.j.k. May 5 at 3:20
1  
I posted new answer here: codereview.stackexchange.com/questions/88850/… :) –  hejcz May 5 at 9:25

Focusing on IntegerFinder:

  • findFirstNumber is returning the object referenced by the field index which is mutable and will be changed by later calls to IntegerFinder's methods. This can lead to serious and difficult-to-debug problems and will usually be reported by automated bug-finding programs. Return a copy of the index instead.
  • The index field looks like it is reset every call to findFirstNumber. It is also set but the value is never used in letterIsANumber. Why is not not a local instance in findFirstNumber?
  • letterIsANumber could just be written as return stringHasANumberAtIndex(text, currentIndex)
  • letterIsANumber could probably be replaced with Character.isDigit(String.codePointAt(index.getValue()))

More generally

  • Some of your classes look like procedures that have been split up into methods and single-use fields. I appreciate that you are experimenting with object-oriented thinking and may have future use in mind for IntegerSubstringer and IntegerFinder, but:
  • Consider passing the text as a constructor argument to IntegerSearch if it is your intent for those objects to represent a state of parsing a string. Otherwise, move the fields into locals to indicate that you don't intend to track the state across (public) method calls.
  • If you haven't already, write some JUnit tests for your class. It will help you ensure the code is correct and may help you come up with better interfaces to your classes.
share|improve this answer

From what I understand you want a simple program which for say input abc123 returns 123, so it extracts all integers from a String.

While your solution works, it really seems an over complication for such a simple task. You can do the same thing using regex in just a few lines

public static void main(String[] args) {
    final StringBuilder buffer = new StringBuilder();
    final String input = "ab123cxyz12xba2a4aaxy1";
    final Pattern p = Pattern.compile("\\d+"); //will look for numbers in the String; matches digits; same as [0-9]
    final Matcher m = p.matcher(input);
    while (m.find()) {
        buffer.append(m.group());
    }
    System.out.println(buffer.toString());
}

You can read and learn more about regex here

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.