Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding approach for Bob #2861

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions exercises/practice/bob/.approaches/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,24 @@
"introduction": {
"authors": [
"bobahop"
],
"contributors": [
"jagdish-15"
]
},
"approaches": [
{
"uuid": "6ca5c7c0-f8f1-49b2-b137-951fa39f89eb",
"slug": "method-based",
"title": "Method based",
"blurb": "Uses boolean functions to check conditions",
"authors": [
"jagdish-15"
],
"contributors": [
"BenjaminGale"
]
},
{
"uuid": "323eb230-7f27-4301-88ea-19c39d3eb5b6",
"slug": "if-statements",
Expand Down
79 changes: 56 additions & 23 deletions exercises/practice/bob/.approaches/introduction.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,58 @@
# Introduction

There are various idiomatic approaches to solve Bob.
A basic approach can use a series of `if` statements to test the conditions.
An array can contain answers from which the right response is selected by an index calculated from scores given to the conditions.
In this exercise, we’re working on a program to determine Bob’s responses based on the tone and style of given messages. Bob responds differently depending on whether a message is a question, a shout, both, or silence. Various approaches can be used to implement this logic efficiently and cleanly, ensuring the code remains readable and easy to maintain.

## General guidance
## General Guidance
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the casing in the headings need changing - there are consistent with most other other approaches. Could you undo the changes to the headings?


Regardless of the approach used, some things you could look out for include
When implementing your solution, consider the following tips to keep your code optimized and idiomatic:

- If the input is trimmed, [`trim()`][trim] only once.
- **Trim the Input Once**: Use [`trim()`][trim] only once at the start to remove any unnecessary whitespace.
- **Use Built-in Methods**: For checking if a message is a question, prefer [`endsWith("?")`][endswith] instead of manually checking the last character.
- **Single Determinations**: Use variables for `questioning` and `shouting` rather than calling these checks multiple times to improve efficiency.
- **DRY Code**: Avoid duplicating code by combining the logic for determining a shout and a question when handling shouted questions. Following the [DRY][dry] principle helps maintain clear and maintainable code.
- **Return Statements**: An early return in an `if` statement eliminates the need for additional `else` blocks, making the code more readable.
- **Curly Braces**: While optional for single-line statements, some teams may require them for readability and consistency.

- Use the [`endsWith()`][endswith] `String` method instead of checking the last character by index for `?`.
## Approach: Method-Based

- Don't copy/paste the logic for determining a shout and for determining a question into determining a shouted question.
Combine the two determinations instead of copying them.
Not duplicating the code will keep the code [DRY][dry].
```java
class Bob {
String hey(String input) {
var inputTrimmed = input.trim();

if (isSilent(inputTrimmed))
return "Fine. Be that way!";
if (isYelling(inputTrimmed) && isAsking(inputTrimmed))
return "Calm down, I know what I'm doing!";
if (isYelling(inputTrimmed))
return "Whoa, chill out!";
if (isAsking(inputTrimmed))
return "Sure.";

return "Whatever.";
}

private boolean isYelling(String input) {
return input.chars()
.anyMatch(Character::isLetter) &&
input.chars()
.filter(Character::isLetter)
.allMatch(Character::isUpperCase);
}

- Perhaps consider making `questioning` and `shouting` values set once instead of functions that are possibly called twice.
private boolean isAsking(String input) {
return input.endsWith("?");
}

- If an `if` statement can return, then an `else if` or `else` is not needed.
Execution will either return or will continue to the next statement anyway.
private boolean isSilent(String input) {
return input.length() == 0;
}
}
```

- If the body of an `if` statement is only one line, curly braces aren't needed.
Some teams may still require them in their style guidelines, though.
This approach defines helper methods for each type of message—silent, yelling, and asking—to keep each condition clean and easily testable. For more details, refer to the [Method-Based Approach][approach-method-based].

## Approach: `if` statements
## Approach: `if` Statements

```java
import java.util.function.Predicate;
Expand Down Expand Up @@ -56,9 +84,9 @@ class Bob {
}
```

For more information, check the [`if` statements approach][approach-if].
This approach utilizes nested `if` statements and a predicate for determining if a message is a shout. For more details, refer to the [`if` Statements Approach][approach-if].

## Approach: answer array
## Approach: Answer Array

```java
import java.util.function.Predicate;
Expand Down Expand Up @@ -86,16 +114,21 @@ class Bob {
}
```

For more information, check the [Answer array approach][approach-answer-array].
This approach uses an array of answers and calculates the appropriate index based on flags for shouting and questioning. For more details, refer to the [Answer Array Approach][approach-answer-array].

## Which Approach to Use?

Choosing between the method-based approach, `if` statements, and answer array approach can come down to readability and maintainability. Each has its advantages:

## Which approach to use?
- **Method-Based**: Clear and modular, great for readability.
- **`if` Statements**: Compact and straightforward, suited for smaller projects.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking the recommendation for smaller projects might be unnecessary - if its suitable for smaller projects, why wouldn't it be suitable for mid-size or larger projects?

Suggested change
- **`if` Statements**: Compact and straightforward, suited for smaller projects.
- **`if` Statements**: Compact and straightforward.

- **Answer Array**: Minimizes condition checks by using indices, efficient for a variety of responses.

Since benchmarking with the [Java Microbenchmark Harness][jmh] is currently outside the scope of this document,
the choice between `if` statements and answers array can be made by perceived readability.
Experiment with these approaches to find the balance between readability and performance that best suits your needs.

[trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim()
[endswith]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#endsWith(java.lang.String)
[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
[approach-method-based]: https://exercism.org/tracks/java/exercises/bob/approaches/method-based
[approach-if]: https://exercism.org/tracks/java/exercises/bob/approaches/if-statements
[approach-answer-array]: https://exercism.org/tracks/java/exercises/bob/approaches/answer-array
[jmh]: https://github.com/openjdk/jmh
89 changes: 89 additions & 0 deletions exercises/practice/bob/.approaches/method-based/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Method-Based Approach

In this approach, the different conditions for Bob’s responses are separated into dedicated private methods within the `Bob` class. This method-based approach improves readability and modularity by organizing each condition check into its own method, making the main response method easier to understand and maintain.

The main `hey` method determines Bob’s response by delegating each condition to a helper method (`isSilent`, `isYelling`, and `isAsking`), each encapsulating specific logic.

## Explanation

This approach simplifies the main method `hey` by breaking down each response condition into helper methods:

1. **Trimming the Input**:
The `input` is trimmed using the `String` [`trim()`][trim] method to remove any leading or trailing whitespace. This helps to accurately detect if the input is empty and should prompt a `"Fine. Be that way!"` response.

2. **Delegating to Helper Methods**:
Each condition is evaluated using the following helper methods:

- **`isSilent`**: Checks if the trimmed input has no characters.
- **`isYelling`**: Checks if the input is all uppercase and contains at least one alphabetic character, indicating shouting.
- **`isAsking`**: Verifies if the trimmed input ends with a question mark.

This modular approach keeps each condition encapsulated, enhancing code clarity.

3. **Order of Checks**:
The order of checks within `hey` is important:
- Silence is evaluated first, as it requires an immediate response.
- Shouted questions take precedence over individual checks for yelling and asking.
- Yelling comes next, requiring its response if not combined with a question.
- Asking (a non-shouted question) is checked afterward.

This ordering ensures that Bob’s response matches the expected behavior without redundancy.

## Code structure

```java
class Bob {
String hey(String input) {
var inputTrimmed = input.trim();

if (isSilent(inputTrimmed))
return "Fine. Be that way!";
if (isYelling(inputTrimmed) && isAsking(inputTrimmed))
return "Calm down, I know what I'm doing!";
if (isYelling(inputTrimmed))
return "Whoa, chill out!";
if (isAsking(inputTrimmed))
return "Sure.";

return "Whatever.";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I think this approach is the same as the existing if-statement approach. The only difference is that the conditions are in methods instead of variables. Is there something else that differentiates this approach from the other ones?

Copy link
Contributor Author

@jagdish-15 jagdish-15 Nov 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @kahgoh for your feedback! I understand your point, and while both the method-based approach and the if statement approach use condition checks, there are a few key differences that I think set the method-based approach apart:

  1. Separation of Concerns:

    • In the method-based approach, I’ve separated the logic for determining if the message is a question, shouting, or silent into individual methods (isYelling(), isAsking(), isSilent()). This makes the main hey() method cleaner and focused on handling the flow of responses.
    • In the if approach, the logic is directly embedded in the main method, which could become harder to read and maintain as more checks are added.
  2. Improved Readability:

    • With the helper methods, it’s clear what each condition is doing without having to dive into complex inline if statements. For example, isYelling() immediately tells you that it’s checking for shouting behaviour, and isAsking() clarifies the question check.
    • This helps future readers (and even future maintainers) quickly grasp the purpose of each check.
  3. Maintainability:

    • While maintainability may not be a major concern for these one-time exercises, the method-based approach provides a structure that makes it easier to modify individual checks without cluttering the main method. This is generally a good practice for ensuring clean, readable, and maintainable code, even in simple exercises.
  4. Scalability:

    • While this specific exercise may not need to scale in the future, separating the logic into individual methods makes it much easier to extend or modify if more conditions are added down the line. Even though we don’t expect more checks here, structuring code this way is generally a good practice for scalability in larger, more complex applications.

In summary, while both approaches work, the method-based approach tends to be cleaner, more modular, and easier to extend. It may seem similar on the surface, but the structure I’ve chosen is designed with maintainability and scalability in mind, which are good practices even for small exercises like this one.

If you have any suggestions or further insights on this approach, I'd be happy to hear them!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, it has taken me quite a while to get back to this one. I think I can see where you are coming from. Looking around, I noticed Python's approach has two similar if approaches for Bob (if statements and if statements nested). If we were to proceed this approach, perhaps we could do something similar by naming the approach in a similar way to the if statements (for example, if statements with checks in methods).

Btw, looking at the points mentioned under General guidance, there is a point about determining the yelling and questioning once: Use variables for questioning and shouting rather than calling these checks multiple times to improve efficiency. In your example solution, you calling the method again, so it is going to be recalculated. But, my worry about putting them in variables is that it will look even closer to the current if statements approach.

I think the way you've currently is fine as it is, but may be worth pointing out this short coming in the introduction's Which approach to use? .

}

private boolean isYelling(String input) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the intro and other approaches for this exercise, we have used the term "shouting" instead of "yelling". I'd suggest using the same terminology to keep it consistent within the approaches.

return input.chars()
.anyMatch(Character::isLetter) &&
input.chars()
.filter(Character::isLetter)
.allMatch(Character::isUpperCase);
}

private boolean isAsking(String input) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, we've used the term "questioning" in the intro and other approaches.

return input.endsWith("?");
}

private boolean isSilent(String input) {
return input.length() == 0;
}
}
```

## Advantages of the Method-Based Approach

- **Readability**: Each method is clearly responsible for a specific condition, making the main response logic easy to follow.
- **Maintainability**: Changes to a condition can be confined to its method, minimizing impacts on the rest of the code.
- **Code Reusability**: Helper methods can be reused or adapted easily if new conditions are added in the future.

## Considerations

- **Efficiency**: While this approach introduces multiple method calls, it enhances readability significantly, which is often more valuable in non-performance-critical applications.
- **Error Prevention**: This approach avoids redundant code, reducing the risk of maintenance errors.

## Shortening Condition Checks

If each `if` statement body is only a single line, braces can be omitted, or the test expression and result could be placed on a single line. However, [Java Coding Conventions][coding-conventions] recommend always using curly braces for error prevention and easier future modifications.

### Alternative: Inline Helper Methods

For smaller projects, consider implementing helper methods inline or as lambdas, though this might reduce readability.

[trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim()
[coding-conventions]: https://www.oracle.com/java/technologies/javase/codeconventions-statements.html#449
8 changes: 8 additions & 0 deletions exercises/practice/bob/.approaches/method-based/snippet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
if (isSilent(inputTrimmed))
return "Fine. Be that way!";
if (isYelling(inputTrimmed) && isAsking(inputTrimmed))
return "Calm down, I know what I'm doing!";
if (isYelling(inputTrimmed))
return "Whoa, chill out!";
if (isAsking(inputTrimmed))
return "Sure.";