Skip to content

Add java code quality examples#49

Open
amazon-pratik wants to merge 1 commit into
mainfrom
feature/add-java-code-quality
Open

Add java code quality examples#49
amazon-pratik wants to merge 1 commit into
mainfrom
feature/add-java-code-quality

Conversation

@amazon-pratik
Copy link
Copy Markdown
Owner

  • Added code quality examples for java
  • Includes error handling, logging, naming, performance, and readability/maintainability examples
  • Contains both compliant and non-compliant code samples

- Added code quality examples for java
- Includes error handling, logging, naming, performance, and readability/maintainability examples
- Contains both compliant and non-compliant code samples
@amazon-q-developer
Copy link
Copy Markdown

⏳ I'm reviewing this pull request for security vulnerabilities and code quality issues. I'll provide an update when I'm done

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @amazon-pratik, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

I've added a comprehensive set of Java code quality examples to the repository. This pull request introduces both compliant and non-compliant code samples across several key areas: error handling, logging, naming conventions, and performance optimization. The aim is to provide clear illustrations of good and bad practices to guide developers in writing more robust, maintainable, and efficient Java applications.

Highlights

  • Error Handling Best Practices: I've introduced new Java code examples that demonstrate both good and bad practices in error handling. The non-compliant examples show common pitfalls like silent catches or missing exception handling, while the compliant ones illustrate robust error management using try-with-resources, specific exception handling, and proper logging with SLF4J.
  • Effective Logging Strategies: This PR includes examples contrasting inefficient logging with effective logging strategies. Non-compliant code often uses System.out.println or logs sensitive information directly. Compliant examples demonstrate the use of SLF4J with appropriate log levels (e.g., INFO, DEBUG, WARN, ERROR) and dedicated loggers (e.g., securityLogger, auditLogger) to ensure clarity, security, and performance.
  • Java Naming Conventions: I've added examples to highlight the importance of clear and descriptive naming conventions in Java. The non-compliant code uses vague or abbreviated names for variables, methods, and classes, making the code harder to understand. The compliant examples showcase how meaningful and consistent naming significantly improves code readability and maintainability for future developers.
  • Performance Optimization Techniques: This pull request provides examples of common performance bottlenecks in Java and their optimized solutions. Non-compliant code often uses inefficient algorithms or data structures (e.g., String concatenation in loops, manual array comparisons). Compliant examples demonstrate using built-in optimized library methods (e.g., StringBuilder, Arrays.equals), Java Streams, and memoization techniques to enhance application performance.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds a comprehensive set of Java code examples demonstrating both compliant and non-compliant code for various quality aspects like error handling, logging, naming, and performance. The examples are well-structured and clearly separated. My review focuses on the non-compliant examples, providing feedback and suggestions to align them with best practices. Key areas for improvement include robust error handling with proper resource management, secure logging practices to avoid exposing sensitive data, adherence to clear and descriptive naming conventions, and significant performance optimizations.

Comment on lines +9 to +12
public int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

This recursive implementation of Fibonacci has an exponential time complexity, O(2^n), because it recomputes the same values multiple times. This will be extremely slow for even moderately large values of n. Use memoization or an iterative approach to improve performance.

Suggested change
public int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
private Map<Integer, Integer> memo = new HashMap<>();
public int fibonacci(int n) {
if (n <= 1) return n;
if (memo.containsKey(n)) {
return memo.get(n);
}
int result = fibonacci(n - 1) + fibonacci(n - 2);
memo.put(n, result);
return result;
}

Comment on lines +9 to +15
public List<User> getAllUsers() {
List<User> users = new ArrayList<>();
for (int i = 1; i <= 1000; i++) {
User user = database.findUserById(i);
users.add(user);
}
return users;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

This method exhibits the N+1 query problem. It iterates and makes a separate database call for each user ID, which is highly inefficient and can cause significant performance issues with a large number of users. It's better to fetch all users with a single query.

    public List<User> getAllUsers() {
        return database.findAllUsers();
    }

Comment on lines +9 to +15
public String replaceAll(String text, String oldChar, String newChar) {
String result = text;
while (result.contains(oldChar)) {
result = result.replace(oldChar, newChar);
}
return result;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

Using String.replace() inside a while loop can be inefficient and may lead to an infinite loop if the replacement string contains the old string (e.g., replacing 'a' with 'ba'). The String.replace() method already replaces all occurrences of the target sequence, so the loop is unnecessary.

    public String replaceAll(String text, String oldChar, String newChar) {
        return text.replace(oldChar, newChar);
    }

Comment on lines +23 to +25
public Class<?> getClass(String className) {
return Class.forName(className);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

Class.forName(className) throws a checked ClassNotFoundException, which must be declared or handled. Failing to do so will result in a compilation error.

    public Class<?> getClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            logger.error("Failed to find class: " + className, e);
            return null;
        }
    }

Comment on lines +23 to +26
public void compressFile(byte[] data, String outputFile) {
GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(outputFile));
gzos.write(data);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The GZIPOutputStream is not closed after being used, which can lead to resource leaks and corrupted files as the stream might not be flushed properly. Additionally, GZIPOutputStream and FileOutputStream constructors, as well as the write method, can throw IOException, which must be handled.

    public void compressFile(byte[] data, String outputFile) {
        try (GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(outputFile))) {
            gzos.write(data);
        } catch (IOException e) {
            logger.error("Failed to compress file: " + outputFile, e);
        }
    }

Comment on lines +23 to +25
public double calculate(int a, int b) {
return a / b;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Division by zero will cause an ArithmeticException at runtime. It's crucial to validate the divisor b before performing the division.

    public double calculate(int a, int b) {
        if (b == 0) {
            throw new IllegalArgumentException("Divisor cannot be zero.");
        }
        return (double) a / b;
    }

Comment on lines +5 to +8
public void doSomething() {
String text = "hello";
System.out.println(text);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The method name doSomething is too generic and does not convey the method's purpose. Similarly, the variable name text is not very descriptive. Using more specific names improves code readability and maintainability.

Suggested change
public void doSomething() {
String text = "hello";
System.out.println(text);
}
public void generateUserReport() {
String reportContent = "hello";
System.out.println(reportContent);
}

Comment on lines +5 to +10
int a;
String s;

public void m() {
int x = 5;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Variable and method names like a, s, m, and x are not descriptive and make the code difficult to understand and maintain. Names should clearly indicate the purpose of the variable or method.

Comment on lines +9 to +15
public boolean arraysEqual(int[] arr1, int[] arr2) {
if (arr1.length != arr2.length) return false;
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) return false;
}
return true;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This implementation of array comparison is done manually. While correct, it's more verbose and potentially less performant than the standard library method Arrays.equals(), which is often implemented in native code for better performance.

    public boolean arraysEqual(int[] arr1, int[] arr2) {
        return Arrays.equals(arr1, arr2);
    }

Comment on lines +9 to +14
public void processMap(Map<String, String> map) {
for (String key : map.keySet()) {
String value = map.get(key);
System.out.println(key + ": " + value);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Iterating over a map using keySet() and then calling get(key) for each key is less efficient than iterating over the entrySet(). Iterating over the entry set avoids the extra hash lookup to retrieve the value for each key.

Suggested change
public void processMap(Map<String, String> map) {
for (String key : map.keySet()) {
String value = map.get(key);
System.out.println(key + ": " + value);
}
}
public void processMap(Map<String, String> map) {
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + ": " + value);
}
}

import java.util.*;

public class MessageBuffer46 {
private StringBuilder messageBuffer;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Description: The messageBuffer field is not initialized, which may lead to a NullPointerException when appendToMessageBuffer is called. Initialize the messageBuffer in the constructor or use a lazy initialization approach in the appendToMessageBuffer method.

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The fix addresses the issue of an uninitialized messageBuffer field by adding a constructor to the MessageBuffer46 class. In the constructor, we initialize the messageBuffer with a new StringBuilder instance. This ensures that the messageBuffer is properly initialized when a new MessageBuffer46 object is created, preventing potential NullPointerExceptions when appendToMessageBuffer is called.

Suggested change
private StringBuilder messageBuffer;
public class MessageBuffer46 {
private StringBuilder messageBuffer;
public MessageBuffer46() {
messageBuffer = new StringBuilder();
}
public void appendToMessageBuffer(String messageFragment) {
messageBuffer.append(messageFragment);
}

private Connection connection;

public Object callMethod(Object obj, String methodName) {
Method method = obj.getClass().getMethod(methodName);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Description: A potential code injection vulnerability has been detected on this line, where untrusted input is passed to a method that may execute arbitrary csharp code. This issue allows attackers to inject and execute arbitrary code within the application, which could lead to unauthorized access to sensitive data or other malicious actions. To mitigate this, ensure that all user-supplied input is properly sanitized and validated before being processed. Avoid passing untrusted input to methods like eval, send, or system that can execute arbitrary code. Where possible, use safer alternatives such as parameterized queries or more controlled methods for handling user input. Learn more

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The fix implements a whitelist of allowed method names and checks if the provided methodName is in the list before invoking it. This prevents arbitrary method execution and mitigates the code injection vulnerability.

Suggested change
Method method = obj.getClass().getMethod(methodName);
// import java.lang.reflect.Method;
// import java.util.Arrays;
// import java.util.List;
public Object callMethod(Object obj, String methodName) {
List<String> allowedMethods = Arrays.asList("toString", "hashCode", "equals");
if (allowedMethods.contains(methodName)) {
Method method = obj.getClass().getMethod(methodName);
return method.invoke(obj);
}
throw new SecurityException("Method not allowed");
}

private Connection connection;

public Class<?> getClass(String className) {
return Class.forName(className);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Description: A potential code injection vulnerability has been detected on this line, where untrusted input is passed to a method that may execute arbitrary csharp code. This issue allows attackers to inject and execute arbitrary code within the application, which could lead to unauthorized access to sensitive data or other malicious actions. To mitigate this, ensure that all user-supplied input is properly sanitized and validated before being processed. Avoid passing untrusted input to methods like eval, send, or system that can execute arbitrary code. Where possible, use safer alternatives such as parameterized queries or more controlled methods for handling user input. Learn more

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The fix implements input validation using a regular expression to ensure that the className parameter contains only alphanumeric characters, dots, underscores, and dollar signs, which are valid characters for Java class names. If the input doesn't match this pattern, an IllegalArgumentException is thrown.

Suggested change
return Class.forName(className);
// import java.util.regex.Pattern
public Class<?> getClass(String className) {
if (Pattern.matches("^[a-zA-Z0-9._$]+$", className)) {
return Class.forName(className);
}
throw new IllegalArgumentException("Invalid class name");
}


public Class<?> getClass(String className) {
try {
return Class.forName(className);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Description: A potential code injection vulnerability has been detected on this line, where untrusted input is passed to a method that may execute arbitrary csharp code. This issue allows attackers to inject and execute arbitrary code within the application, which could lead to unauthorized access to sensitive data or other malicious actions. To mitigate this, ensure that all user-supplied input is properly sanitized and validated before being processed. Avoid passing untrusted input to methods like eval, send, or system that can execute arbitrary code. Where possible, use safer alternatives such as parameterized queries or more controlled methods for handling user input. Learn more

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The fix adds input validation using a regex pattern to ensure that the className contains only valid characters before passing it to Class.forName(). This prevents potential code injection by rejecting malformed or potentially malicious input.

Suggested change
return Class.forName(className);
// Import statements
// import java.util.regex.Pattern;
// import java.util.regex.Matcher;
public Class<?> getClass(String className) {
try {
// Validate className using regex to ensure it only contains valid characters
Pattern pattern = Pattern.compile("^[a-zA-Z0-9._$]+$");
Matcher matcher = pattern.matcher(className);
if (!matcher.matches()) {
throw new IllegalArgumentException("Invalid class name");
}
return Class.forName(className);
} catch (ClassNotFoundException | IllegalArgumentException e) {
logger.error("Class loading failed: " + className, e);
return null;
}
}


public Object callMethod(Object obj, String methodName) {
try {
Method method = obj.getClass().getMethod(methodName);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Description: A potential code injection vulnerability has been detected on this line, where untrusted input is passed to a method that may execute arbitrary csharp code. This issue allows attackers to inject and execute arbitrary code within the application, which could lead to unauthorized access to sensitive data or other malicious actions. To mitigate this, ensure that all user-supplied input is properly sanitized and validated before being processed. Avoid passing untrusted input to methods like eval, send, or system that can execute arbitrary code. Where possible, use safer alternatives such as parameterized queries or more controlled methods for handling user input. Learn more

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The fix implements a whitelist of allowed method names and checks if the provided methodName is in the list before invoking it, preventing arbitrary method execution.

Suggested change
Method method = obj.getClass().getMethod(methodName);
// Import java.util.Arrays for the asList method
// Import java.util.List for type declaration
public Object callMethod(Object obj, String methodName) {
try {
List<String> allowedMethods = Arrays.asList("toString", "hashCode", "equals");
if (!allowedMethods.contains(methodName)) {
throw new SecurityException("Method not allowed: " + methodName);
}
Method method = obj.getClass().getMethod(methodName);
return method.invoke(obj);
} catch (ReflectiveOperationException | SecurityException e) {
logger.error("Method invocation failed: " + methodName, e);
return null;
}

import java.util.*;

public class ListManager05 {
List<String> list;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Description: The 'list' field is not initialized, which may lead to a NullPointerException when add() is called. Initialize the 'list' field in the constructor or use a field initializer.

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The fix initializes the 'list' field with a new ArrayList<>() instance. This addresses the potential NullPointerException issue by ensuring that the list is always initialized before the add() method is called. The fix also makes the 'list' field private for better encapsulation.

Suggested change
List<String> list;
import java.util.*;
public class ListManager05 {
private List<String> list = new ArrayList<>();
public void add(String item) {
list.add(item);


public void auditAction(String action) {
// No audit logging
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning

Description: The auditAction method is not implementing any audit logging despite its name and purpose. Implement audit logging in the auditAction method using the auditLogger.

Severity: High

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The fix implements audit logging in the auditAction method using the auditLogger. It retrieves the current user and logs the action performed by that user. This addresses the comment by adding the missing audit logging functionality to the method, which was previously empty.

Suggested change
}
private static final Logger metricsLogger = LoggerFactory.getLogger("METRICS");
public void auditAction(String action) {
String user = getCurrentUser();
auditLogger.info("User {} performed action: {}", user, action);
}
private String getCurrentUser() {


public boolean removeFile(String filename) {
try {
Files.delete(Paths.get(filename));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning

Description: Path traversal vulnerability detected. User-controlled input in file paths
can allow attackers to access files outside intended directories using
../ sequences. Secure your code by using framework functions like
getCanonicalPath(), normalize(), or validating paths with
.startsWith() checks. Learn more:
https://cwe.mitre.org/data/definitions/22.html

Severity: High

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The fix normalizes the file path, checks if it's within the base directory (current working directory), and only proceeds with deletion if it's safe. This prevents path traversal attacks by disallowing access to files outside the intended directory.

Suggested change
Files.delete(Paths.get(filename));
// import java.nio.file.Path;
// import java.nio.file.Paths;
// Import Path and Paths for file path manipulation and validation
public boolean removeFile(String filename) {
try {
Path filePath = Paths.get(filename).normalize();
Path basePath = Paths.get(System.getProperty("user.dir")).normalize();
if (!filePath.startsWith(basePath)) {
logger.error("Attempted to access file outside base directory: " + filename);
return false;
}
Files.delete(filePath);
return true;
} catch (IOException e) {
logger.error("File removal failed: " + filename, e);
return false;
}
}


public boolean duplicateFile(String source, String dest) {
try {
Files.copy(Paths.get(source), Paths.get(dest));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning

Description: Path traversal vulnerability detected. User-controlled input in file paths
can allow attackers to access files outside intended directories using
../ sequences. Secure your code by using framework functions like
getCanonicalPath(), normalize(), or validating paths with
.startsWith() checks. Learn more:
https://cwe.mitre.org/data/definitions/22.html

Severity: High

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The fix normalizes and validates both source and destination paths, ensuring they are within the allowed directory (current working directory) before performing the file copy operation. This prevents path traversal attacks by rejecting paths that attempt to access files outside the intended directory.

Suggested change
Files.copy(Paths.get(source), Paths.get(dest));
// import java.nio.file.Path;
// import java.nio.file.Paths;
// Importing Path and Paths for file path manipulation and validation
public boolean duplicateFile(String source, String dest) {
try {
Path sourcePath = Paths.get(source).normalize().toAbsolutePath();
Path destPath = Paths.get(dest).normalize().toAbsolutePath();
if (!sourcePath.startsWith(System.getProperty("user.dir")) ||
!destPath.startsWith(System.getProperty("user.dir"))) {
throw new SecurityException("Access denied: File path is outside the allowed directory");
}
Files.copy(sourcePath, destPath);
return true;
} catch (IOException | SecurityException e) {
logger.error("File duplication failed: " + source + " -> " + dest, e);
return false;
}
}

public void processArray(int[] array) {
for (int i = 0; i < array.length; i++) {
for (int j = i + 1; j < array.length; j++) {
System.out.println(array[i] + array[j]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Warning

Description: It appears that you are using println() rather than a dedicated logging facility makes it difficult to monitor the program behavior. We recommend to use a Java logging facility rather than System.out or System.err.

Learn more

Severity: High

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The fix replaces System.out.println() with a proper logging statement using SLF4J (logger.debug()). This change improves the ability to monitor and control program output, and allows for more flexible log management.

Suggested change
System.out.println(array[i] + array[j]);
// Import statements for logging
/*
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
These imports are necessary to use the SLF4J logging framework, which provides a more flexible and configurable logging solution compared to System.out.println().
*/
private static final Logger logger = LoggerFactory.getLogger(YourClassName.class);
public void processArray(int[] array) {
for (int i = 0; i < array.length; i++) {
for (int j = i + 1; j < array.length; j++) {
logger.debug("Sum of elements: {}", array[i] + array[j]);
}
}
}

@amazon-q-developer
Copy link
Copy Markdown

✅ I finished the code review, and left comments with the issues I found. I will now generate code fix suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant