Java method to load random paragraph from text file Looking for some critique for this code. but ultimately I would like to be directed to resources/books that address my code writing weaknesses, in terms of style structure an convention :

The task is to create code that would read a file that has several questions and answers, each question starts with the word "Question", and out put the question and a number of lines from the answer that is not greater than 40 lines.

my code method below is meant to:


read all lines into an List.
select a random start line in the list.
check id the line starting with the string "Question".
check if the remaining list elements are more than 40 (I've appended 40 empty lines to the end of the file, to make sure to include the last lines of the file).
create a string buffer to include maximum the 39 lines which are present after the start line, the one that contains "Question" , if the the word "Question" appears before the 40th line, then we we end the text captured at the preceding line (creating a text block).
return that (text block).




public static String readJavaInterview() throws IOException{

        List&lt;String&gt; list = Files.readAllLines(new File("src/lists/JavaCodingInterview.txt").toPath(), Charset.defaultCharset() );
        List&lt;String&gt; subList = new ArrayList&lt;&gt;();
        int start=0 , end= 0;


        Random rand = new Random();
        int n = rand.nextInt(list.size());
        StringBuffer textBlock= new StringBuffer();
        Pattern p = Pattern.compile("Question");
        Matcher m;

        textBlock.append("Some Java Interview Qs :\n");

        if (n + 40 &lt; list.size()){

            subList = list.subList(n, n + 40);

            for (int i = 0 ; i &lt; subList.size() - 1 ; i++){
                m = p.matcher(subList.get(i).toString());
                boolean b = m.find();

                if (b)
                    {

                    start= i + 1;
                    textBlock.append(subList.get(i) + "\n");
                    break;
                }else{
                    continue;
                }
            }

            for(int j = start ; j &lt; start + 39; j++){
                m = p.matcher(subList.get(j).toString());
                boolean b = m.find();
                if (!b){

                    textBlock.append(subList.get(j) + "\n");

                }else{

                    break;
                }
            }

        }else{
            return "";
        }

        return textBlock.toString();
    }


Would this be how you would do it?
Some assumptions based on the limited explanation of the question you posted:


A new question starts with the word "Question" at the beginning of the line
The first line in the file starts with "Question". (not really required but otherwise we have to add a check).
A question can have any number of lines. It ends either at the end of the file, or right before a line that starts with the word "Question" (at which point the next question starts).
If a question contains more than 40 lines, we are only interested in the first 40. The rest of the question is ignored.
You want to randomly select a single question from the file as a result of the program




What I would do is split this up into 2 main parts.

1) read the file and split it up into separate "Questions"
2) select a random question

To me this looks a lot like I want to have List&lt;Question&gt; questions somewhere. Where Question is a new class to hold a single question.

Once we have that step 2 becomes almost trivial. (You've shown in your code you know how to do this).

Let's split step 1 up into specific parts as well.

1) read a file line by line
2) for each line:
2.a) Check if it is the start of a new Question.
2.b) add it to the current question if it's not 40 lines yet, otherwise ignore

Let's skip step 1 for a moment and look at how to process each line. (We'll come back to reading the file later).

I suggest creating a sort of "factory" class. This class will store that List&lt;Question&gt; that I wanted, and to make it easy for ourselves it will also store the Question currentQuestion. That way we can do step 2.b by just adding the line to the currentQuestion.

But first let's look at the Question class:

class Question {
    private List&lt;String&gt; lines = new ArrayList&lt;&gt;();

    public void addLine(String line){
        if(lines.size()&gt;=40){
            return;
        }
        lines.add(line);
    }

    public List&lt;String&gt; getLines(){
        //we might want to make a new list here to prevent other classes from modifying it.
        return lines;
    }
}


Notice how I also made sure to only keep track of the first 40 lines. By doing this here, we can safely keep calling the add method while parsing all the lines without worying about it anymore.

Now for that factory class. This turns out to be rather easy as well:

public class InterviewQuestionFactory {
    private List&lt;Question&gt; questions = new ArrayList&lt;&gt;();
    private Question currentQuestion = null;

    public InterviewQuestionFactory(){
        //TODO read lines from file
        // for each ... {
        String line = "";
        process(line);
        //}
    }

    private void process(String line){
        if (line.startsWith("Question")) {
            currentQuestion = new Question();
        }
        currentQuestion.addLine(line);
    }
}


That was everything for step 2. All that's left is step 1 and we're done.

For step 1 here it's best to take a look at this post. Your current solution to read all lines at once might not work for large files.
Since java 8 you can easily do something like this instead:

try (Stream&lt;String&gt; lines = Files.lines(Paths.get(filename), Charset.defaultCharset())) {
    lines.forEachOrdered(line -&gt; process(line));
}


And that's how I would have solved the problem. I left some parts up for you to actually complete but it should be almost trivial at this point.



To complete this review let's point out some things about your current piece of art:

doesn't work as intended

If your random start happens to be the line just after the start of a really long question you don't return anything.

unnecessary whitespace

this:

for (int i = 0 ; i &lt; subList.size() - 1 ; i++){
    m = p.matcher(subList.get(i).toString());
    boolean b = m.find();

    if (b)
        {

        start= i + 1;
        textBlock.append(subList.get(i) + "\n");
        break;
    }else{


should look like this instead:

for (int i = 0; i &lt; subList.size() - 1; i++) {
    m = p.matcher(subList.get(i).toString());
    boolean b = m.find();
    if (b) {
        start = i + 1;
        textBlock.append(subList.get(i) + "\n");
        break;
    } else {


variable names

In that last bit of code, what is the meaning of the variables m p b and start? This reads like:

You match the string from the sublist to something. If it matches you update start to the next index in the sublist and append the current String to the textblock.

Most of it can be solved by using sublist(i).startsWith("Question") instead. If we then also inline the b to get rid of it entirely and rename start to nextLineIndex it turns into this:

for (int i = 0; i &lt; subList.size() - 1; i++) {
    if (sublist.get(i).startsWith("Question") {
        nextLineIndex = i + 1;
        textBlock.append(subList.get(i) + "\n");
        break;
    } else {


actually the start name was fine if you had set it to start = i instead. And start the following for loop at for(int j = start + 1; ... instead. At least then we would know you start looping at the second line of the question.

string.toString()

sublist.get(i) is already a String. No need to call toString() on it.