Best Practices and Clean Code in Java
Writing clean code means creating readable, maintainable, and professional software. SOLID principles and best practices guide toward robust and scalable design.
What You'll Learn
- SOLID principles
- Naming conventions
- Code smells and refactoring
- Proper error handling
- Effective documentation
SOLID Principles
// WRONG: class with too many responsibilities
class StudentManager {
void saveStudent(Student s) { /* save to DB */ }
void sendEmail(Student s) { /* send email */ }
String generateReport(Student s) { /* generate report */ }
void validateStudent(Student s) { /* validate data */ }
}
// CORRECT: one responsibility per class
class StudentRepository {
void save(Student s) { /* save to DB */ }
Student find(Long id) { /* search in DB */ }
}
class EmailService {
void send(String to, String message) { /* send */ }
}
class StudentReportGenerator {
String generate(Student s) { /* generate report */ }
}
class StudentValidator {
boolean isValid(Student s) { /* validate */ }
}
// WRONG: modification needed for new types
class PaymentCalculator {
double calculate(String type, double amount) {
if (type.equals("CARD")) {
return amount * 0.98; // 2% fee
} else if (type.equals("PAYPAL")) {
return amount * 0.97; // 3% fee
}
// Add new else-if for each type...
return amount;
}
}
// CORRECT: open for extension, closed for modification
interface PaymentMethod {
double calculateFinalAmount(double amount);
}
class CardPayment implements PaymentMethod {
public double calculateFinalAmount(double amount) {
return amount * 0.98;
}
}
class PayPalPayment implements PaymentMethod {
public double calculateFinalAmount(double amount) {
return amount * 0.97;
}
}
// New method: just add a class
class CryptoPayment implements PaymentMethod {
public double calculateFinalAmount(double amount) {
return amount * 0.99;
}
}
// WRONG: subclass violates the contract
class Rectangle {
protected int width;
protected int height;
public void setWidth(int w) { width = w; }
public void setHeight(int h) { height = h; }
public int getArea() { return width * height; }
}
class Square extends Rectangle {
public void setWidth(int w) {
width = w;
height = w; // Violates expected behavior!
}
}
// CORRECT: composition instead of problematic inheritance
interface Shape {
int getArea();
}
class RectangleCorrect implements Shape {
private final int width;
private final int height;
RectangleCorrect(int width, int height) {
this.width = width;
this.height = height;
}
public int getArea() { return width * height; }
}
class SquareCorrect implements Shape {
private final int side;
SquareCorrect(int side) {
this.side = side;
}
public int getArea() { return side * side; }
}
// WRONG: interface too large
interface Worker {
void work();
void eat();
void sleep();
}
class Robot implements Worker {
public void work() { /* ok */ }
public void eat() { /* not applicable! */ }
public void sleep() { /* not applicable! */ }
}
// CORRECT: segregated interfaces
interface Workable {
void work();
}
interface LivingBeing {
void eat();
void sleep();
}
class Human implements Workable, LivingBeing {
public void work() { /* implement */ }
public void eat() { /* implement */ }
public void sleep() { /* implement */ }
}
class RobotCorrect implements Workable {
public void work() { /* implement only what's needed */ }
}
// WRONG: depends on concrete implementation
class NotificationService {
private EmailSender emailSender = new EmailSender();
void notify(String message) {
emailSender.send(message); // Tight coupling!
}
}
// CORRECT: depends on abstraction
interface NotificationSender {
void send(String message);
}
class EmailSenderImpl implements NotificationSender {
public void send(String message) { /* email */ }
}
class SmsSender implements NotificationSender {
public void send(String message) { /* sms */ }
}
class NotificationServiceCorrect {
private final NotificationSender sender;
// Dependency Injection
NotificationServiceCorrect(NotificationSender sender) {
this.sender = sender;
}
void notify(String message) {
sender.send(message);
}
}
Naming Conventions
// WRONG: unclear names
int d; // what does it represent?
String s;
List<String> list;
void proc(int x);
// CORRECT: descriptive names
int daysUntilExpiration;
String studentName;
List<String> activeEmails;
void calculateGradeAverage(int studentId);
// Java conventions
class ClassName {} // PascalCase
interface Calculable {} // PascalCase
void methodName() {} // camelCase
int localVariable; // camelCase
static final int CONSTANT = 10; // UPPER_SNAKE_CASE
// Booleans: prefix is/has/can
boolean isActive;
boolean hasPermission;
boolean canModify;
// Collections: plural
List<Student> students;
Map<String, Integer> gradesBySubject;
// Methods: verb + noun
void saveStudent(Student s);
Student findStudentById(Long id);
boolean isStudentActive(Student s);
List<Student> getActiveStudents();
Code Smells and Refactoring
// CODE SMELL: Method too long
void processOrder(Order o) {
// 50+ lines of code...
}
// REFACTORING: Extract methods
void processOrder(Order o) {
validateOrder(o);
calculateTotal(o);
applyDiscounts(o);
saveOrder(o);
sendConfirmation(o);
}
// CODE SMELL: Magic numbers
if (age > 18 && score > 75) {
// ...
}
// REFACTORING: Named constants
private static final int MINIMUM_AGE = 18;
private static final int PASSING_SCORE = 75;
if (age > MINIMUM_AGE && score > PASSING_SCORE) {
// ...
}
// CODE SMELL: Boolean parameters
void createUser(String name, boolean admin, boolean active);
// REFACTORING: Builder or objects
class UserConfiguration {
private boolean admin;
private boolean active;
// builder pattern...
}
// CODE SMELL: Comments explaining complex code
// Calculate weighted average considering credits
double r = 0;
int t = 0;
for (int i = 0; i < v.length; i++) {
r += v[i] * c[i];
t += c[i];
}
return r / t;
// REFACTORING: Self-documenting code
double calculateWeightedAverage(int[] grades, int[] credits) {
double weightedSum = 0;
int totalCredits = 0;
for (int i = 0; i < grades.length; i++) {
weightedSum += grades[i] * credits[i];
totalCredits += credits[i];
}
return weightedSum / totalCredits;
}
Error Handling
// WRONG: generic catch
try {
// operation
} catch (Exception e) {
// too generic handling
}
// CORRECT: specific catches
try {
// operation
} catch (FileNotFoundException e) {
// handle file not found
} catch (IOException e) {
// handle other I/O errors
}
// WRONG: ignored exception
try {
riskyOperation();
} catch (Exception e) {
// silent - NEVER do this!
}
// CORRECT: at least log
try {
riskyOperation();
} catch (Exception e) {
logger.error("Error during operation", e);
throw new ServiceException("Operation failed", e);
}
// Meaningful custom exceptions
class StudentNotFoundException extends RuntimeException {
private final Long studentId;
StudentNotFoundException(Long id) {
super("Student not found with ID: " + id);
this.studentId = id;
}
public Long getStudentId() {
return studentId;
}
}
// Use Optional instead of null
Optional<Student> findStudent(Long id);
// Caller
Student s = findStudent(id)
.orElseThrow(() -> new StudentNotFoundException(id));
General Best Practices
Golden Rules
- DRY: Don't Repeat Yourself
- KISS: Keep It Simple, Stupid
- YAGNI: You Aren't Gonna Need It
- Fail Fast: Validate input early
- Immutability: Prefer immutable objects
- Composition > Inheritance: More flexible
- Program to interfaces: Not implementations
- Single Level of Abstraction: One level per method
// Well-structured class
public class StudentService {
private final StudentRepository repository;
private final EmailService emailService;
private final StudentValidator validator;
public StudentService(StudentRepository repository,
EmailService emailService,
StudentValidator validator) {
this.repository = Objects.requireNonNull(repository);
this.emailService = Objects.requireNonNull(emailService);
this.validator = Objects.requireNonNull(validator);
}
public Student register(RegistrationRequest request) {
validator.validate(request);
Student student = createStudent(request);
Student saved = repository.save(student);
sendWelcomeEmail(saved);
return saved;
}
private Student createStudent(RegistrationRequest request) {
return Student.builder()
.name(request.getName())
.email(request.getEmail())
.registrationDate(LocalDate.now())
.build();
}
private void sendWelcomeEmail(Student student) {
emailService.send(
student.getEmail(),
"Welcome " + student.getName()
);
}
}







