Professional Java Practices
Clean Methods and Class Design
আপনি একটি free preview lesson দেখছেন।
Lesson Overview
Java syntax জানা আর maintainable Java code লেখা এক জিনিস নয়।
একটি program compile করতে পারে, output-ও ঠিক দিতে পারে, কিন্তু codebase তবুও difficult হতে পারে যদি:
- Methods অনেক বড় হয়
- এক method অনেক unrelated কাজ করে
- Class অনেক responsibility নেয়
- Names vague হয়
- Boolean parameters meaning hide করে
- Business rules scattered থাকে
- Comments দিয়ে confusing code explain করতে হয়
- Data এক object-এ থাকে, behavior অন্য জায়গায় ছড়িয়ে থাকে
Professional Java code-এর একটি major goal হলো:
Make the code easy to understand,
easy to change,
and difficult to misuse.
এই lesson-এ আমরা শিখব:
- Single responsibility
- Cohesion
- Intention-revealing names
- Small focused methods
- Guard clauses
- Method parameters
- Boolean blindness
- Command-query separation
- Avoiding hidden side effects
- Avoiding god classes
- Moving behavior to the right object
- Comments vs expressive code
- Extract method refactoring
- Designing readable application flows
Learning Objectives
এই lesson শেষে আপনি পারবেন:
- Method responsibility identify করতে
- Long methodsকে smaller cohesive methods-এ refactor করতে
- Better method এবং class names choose করতে
- Guard clauses ব্যবহার করতে
- Excessive parameters identify করতে
- Boolean parameter problems explain করতে
- Query এবং command operations distinguish করতে
- God class detect করতে
- Behaviorকে better owner-এর কাছে move করতে
- Comments-এর বদলে expressive code ব্যবহার করতে
- A procedural methodকে cleaner object-oriented design-এ refactor করতে
Clean Code Does Not Mean Short Code
Clean code মানে শুধু:
কম line
না।
এই code short:
if (c != null && c.getS() == 1 && c.getL().size() > 0) {
r.save(c);
}
কিন্তু readable নয়।
Longer version:
if (
course != null
&& course.isReadyToPublish()
) {
repository.save(
course
);
}
এখানে code-এর intent অনেক clearer।
Goal:
Minimum confusion
not:
Minimum characters
A Method Should Have a Clear Purpose
Consider:
public void processCourse(
Course course
) {
// Validate title
// Save course
// Send email
// Generate report
// Update analytics
}
Method name:
processCourse()
কিছুই clearly বলে না।
Methodটি অনেক responsibilityও নিচ্ছে।
Better: Separate Intentions
validateCourse(
course
);
repository.save(
course
);
notificationService.notifyCourseCreated(
course
);
Now each operation communicates a clear purpose।
Single Responsibility
Single responsibility মানে:
A method or class should have one coherent reason to change.
এটা literal rule নয় যে:
Every method must do one line
Every class must have one method
Rather:
Related behavior should stay together.
Unrelated concerns should not be mixed.
Example: Mixed Responsibilities
public void createCourse(
String code,
String title,
long price
) throws IOException {
if (
code == null
|| code.isBlank()
) {
throw new IllegalArgumentException();
}
Course course =
new Course(
code,
title,
price
);
Files.writeString(
Path.of(
"courses",
code + ".txt"
),
title
);
System.out.println(
"Course created"
);
}
This method handles:
Input validation
Domain creation
File persistence
Console output
Too many concerns।
Better Separation
public void createCourse(
CourseCode code,
String title,
long price
) {
Course course =
new Course(
code,
title,
price
);
repository.save(
course
);
}
Elsewhere:
CourseRepository
handles persistence।
Console/UI layer handles presentation।
Cohesion
A cohesive class has methods and fields that belong to one concept।
High cohesion:
Course
contains:
title
status
lessons
addLesson()
publish()
changeTitle()
These all belong to course behavior।
Low Cohesion
public class CourseManager {
public void publishCourse() {
}
public void calculateTax() {
}
public void sendEmail() {
}
public void resizeImage() {
}
public void parseCsv() {
}
}
These responsibilities have little relation।
The class is likely becoming a dumping ground।
Naming Is Part of Design
Weak names:
doWork()
process()
handle()
manage()
executeStuff()
data()
thing()
They hide intent।
Better:
publishCourse()
calculatePrice()
findCourseByCode()
validateEnrollment()
archiveCourse()
A good name tells the reader:
What this code means
Method Names Should Describe Behavior
Weak:
course.update();
Update what?
Better:
course.changeTitle(
newTitle
);
or:
course.publish();
Specific verbs make APIs easier to use correctly।
Boolean Method Names
For boolean-returning methods, names should read like questions।
Good:
isPublished()
hasLessons()
canPublish()
containsCourse()
isEmpty()
Weak:
published()
checkCourse()
validate()
because return meaning may be unclear।
Avoid Misleading Names
Bad:
public boolean validateCourse(
Course course
) {
repository.save(
course
);
return true;
}
A method named:
validate
should not unexpectedly persist data।
Names should match behavior।
Method Size
There is no magical correct line count।
A method is probably too large when:
- You need comments to divide it into sections
- It performs multiple levels of abstraction
- You cannot describe it with one clear sentence
- Variables from the beginning are still manipulated far later
- Testing individual rules is difficult
Long Method Example
public void publishCourse(
Course course
) {
if (course == null) {
throw new IllegalArgumentException(
"Course is required."
);
}
if (
course.getTitle() == null
|| course.getTitle().isBlank()
) {
throw new IllegalStateException(
"Course title is required."
);
}
if (
course.getLessons()
.isEmpty()
) {
throw new IllegalStateException(
"Course must have lessons."
);
}
for (
Lesson lesson
: course.getLessons()
) {
if (
lesson.getContent() == null
|| lesson.getContent()
.isBlank()
) {
throw new IllegalStateException(
"Every lesson must have content."
);
}
}
course.setStatus(
CourseStatus.PUBLISHED
);
repository.save(
course
);
System.out.println(
"Course published."
);
}
Several concerns are mixed।
Move Rules to the Object That Owns Them
The Course knows:
Whether it can be published
Better:
public void publish() {
if (lessons.isEmpty()) {
throw new IllegalStateException(
"Course must have at least one lesson."
);
}
boolean incompleteLesson =
lessons.stream()
.anyMatch(
lesson ->
!lesson.hasContent()
);
if (incompleteLesson) {
throw new IllegalStateException(
"Every lesson must have content."
);
}
status =
CourseStatus.PUBLISHED;
}
Service:
public void publishCourse(
CourseCode courseCode
) {
Course course =
requireCourse(
courseCode
);
course.publish();
repository.save(
course
);
}
Much clearer।
Keep the Same Level of Abstraction
Consider:
public void publishCourse(
CourseCode code
) {
Course course =
repository.findByCode(
code
);
if (course == null) {
throw new CourseNotFoundException(
code
);
}
if (
course.getLessons()
.stream()
.anyMatch(
lesson ->
lesson.getContent()
.trim()
.length()
== 0
)
) {
throw new IllegalStateException();
}
course.publish();
repository.save(
course
);
}
The method mixes:
High-level workflow
Low-level content validation
Better:
public void publishCourse(
CourseCode code
) {
Course course =
requireCourse(
code
);
course.publish();
repository.save(
course
);
}
Now method reads like a workflow।
Extract Method
Suppose:
Course course =
repository.findByCode(
code
);
if (course == null) {
throw new CourseNotFoundException(
code
);
}
appears repeatedly।
Extract:
private Course requireCourse(
CourseCode code
) {
Course course =
repository.findByCode(
code
);
if (course == null) {
throw new CourseNotFoundException(
code
);
}
return course;
}
Then:
Course course =
requireCourse(
code
);
Extract for Meaning, Not Just Reuse
A method does not need to be reused to deserve extraction।
Example:
if (
course.getStatus()
== CourseStatus.DRAFT
&& !course.getLessons()
.isEmpty()
&& course.getTitle()
.length()
>= 10
) {
}
Can become:
if (
course.isReadyForReview()
) {
}
Even if only used once, readability improves।
Guard Clauses
Guard clauses reject invalid conditions early।
Nested style:
public void enroll(
User user,
Course course
) {
if (user != null) {
if (course != null) {
if (!course.isArchived()) {
// real logic
}
}
}
}
Harder to read।
Guard Clause Style
public void enroll(
User user,
Course course
) {
if (user == null) {
throw new IllegalArgumentException(
"User is required."
);
}
if (course == null) {
throw new IllegalArgumentException(
"Course is required."
);
}
if (course.isArchived()) {
throw new IllegalStateException(
"Archived course cannot accept enrollment."
);
}
// main flow
}
Main logic stays at a lower indentation level।
Guard Clauses Are Especially Useful for Preconditions
Examples:
Required parameter missing
Invalid state
Unauthorized operation
Unsupported value
Already completed operation
Reject early, then continue with the happy path।
Avoid Excessive Nesting
Weak:
if (course != null) {
if (course.isPublished()) {
if (user != null) {
if (!alreadyEnrolled) {
// enrollment
}
}
}
}
Better:
requireCourse(
course
);
requireUser(
user
);
if (!course.isPublished()) {
throw new IllegalStateException(
"Course is not published."
);
}
if (alreadyEnrolled) {
throw new IllegalStateException(
"User is already enrolled."
);
}
// enrollment
Method Parameters
A method with too many parameters becomes hard to understand and easy to misuse।
Example:
createCourse(
"JAVA",
"Java",
499000,
true,
false,
8,
"BN",
false
);
What does:
true
false
8
false
mean?
You need to inspect the signature।
Boolean Blindness
Consider:
createCourse(
title,
true
);
Does true mean:
Published?
Featured?
Free?
Visible?
The call site hides meaning।
Better Than Boolean Parameters
Instead of:
course.setPublished(
true
);
prefer behavior:
course.publish();
Instead of:
sendNotification(
user,
false
);
perhaps use:
sendSilentNotification(
user
);
or a meaningful enum/config type।
Enum Instead of Boolean Modes
Weak:
exportCourse(
course,
true
);
Better:
exportCourse(
course,
ExportFormat.FULL
);
Now intent is visible।
Parameter Object
When several values belong together:
createCourse(
code,
title,
description,
price,
language,
duration
);
a request object may help:
CreateCourseCommand command
Example:
public record CreateCourseCommand(
CourseCode code,
String title,
String description,
long priceInPaisa,
String language,
int durationInWeeks
) {
}
Then:
createCourse(
command
);
Do Not Create Parameter Objects for Every Two Parameters
This:
findCourse(
CourseCode code,
boolean includeArchived
);
does not automatically require a new class।
Use parameter objects when values form a meaningful concept or call sites become hard to understand।
Prefer Strong Types
Weak:
void moveLesson(
long courseId,
long lessonId,
long position
)
All parameters are long।
Accidental swap compiles।
Better where appropriate:
void moveLesson(
CourseId courseId,
LessonId lessonId,
LessonPosition position
)
Strong types make invalid calls harder।
Command vs Query
A useful design principle:
Command → changes state
Query → returns information
Examples:
Command:
course.publish();
repository.save(course);
Query:
course.isPublished();
repository.findByCode(code);
Command-Query Separation
A method ideally should not both:
Mutate state
and
Return surprising unrelated information
Weak:
Course publishAndReturnPreviousCourse(
Course course
)
Better to separate responsibilities when practical।
Not an Absolute Rule
Some operations naturally change state and return useful result।
Example:
Map.put(
key,
value
)
returns previous value।
Or:
Files.deleteIfExists(
path
)
returns whether deletion occurred।
The principle is about predictability, not rigid dogma।
Hidden Side Effects
This looks like a query:
Course getCourse(
CourseCode code
)
But imagine it also:
Updates last-accessed timestamp
Writes audit file
Sends analytics event
Now the method has hidden side effects।
Readers assume getters/query methods are cheap and observational unless documented otherwise।
Name Side Effects Clearly
Instead of:
getOrCreateCourse(...)
if creation is significant, make it obvious।
Or split:
findCourse(...)
createCourse(...)
depending on desired semantics।
Avoid God Classes
A god class knows or does too much।
Example:
public class LiveKlassManager {
public void createCourse() {
}
public void enrollUser() {
}
public void processPayment() {
}
public void sendEmail() {
}
public void uploadImage() {
}
public void generateReport() {
}
public void backupDatabase() {
}
}
This class becomes a central dependency for everything।
Problems with God Classes
They create:
- Low cohesion
- High coupling
- Difficult tests
- Frequent merge conflicts
- Huge constructors
- Hard-to-understand changes
- Fragile code
If one class changes for ten unrelated reasons, it probably owns too much।
Split by Responsibility
Instead:
CourseService
EnrollmentService
PaymentService
NotificationService
MediaStorage
ReportGenerator
Each class owns a coherent capability।
Avoid "Manager" as an Automatic Class Name
Names such as:
CourseManager
UserManager
DataManager
SystemManager
can become vague containers।
Sometimes Manager is legitimate, but ask:
What does this class actually do?
Maybe better names are:
CourseService
CourseCatalog
CoursePublisher
EnrollmentRegistry
Service Classes Can Become God Classes Too
Even a class named:
CourseService
can become too large:
create
update
publish
archive
import
export
image upload
search
billing
analytics
recommendations
Feature name alone does not guarantee cohesion।
Split when clear sub-responsibilities emerge।
Move Behavior to the Right Object
Procedural code often asks for data and then makes decisions elsewhere।
Example:
if (
course.getStatus()
== CourseStatus.DRAFT
&& course.getLessons()
.size()
> 0
) {
course.setStatus(
CourseStatus.PUBLISHED
);
}
This logic belongs to:
Course
Better:
course.publish();
Tell, Don't Ask
A useful object-oriented idea:
Instead of:
Ask object for all its internal state
Decide outside
Modify object
prefer:
Tell object what you want it to do
Let it enforce its rules
Example:
course.publish();
instead of manually manipulating status।
But Do Not Put Everything into Domain Objects
This would be bad:
course.sendEmail();
course.saveToDatabase();
course.uploadVideo();
These require external infrastructure and do not belong to the Course entity।
Good domain behavior:
course.publish();
course.addLesson();
course.changeTitle();
Infrastructure:
repository.save(course);
notificationService.notifyPublished(course);
Data Ownership Helps Decide Method Placement
Ask:
Who owns the information needed for this rule?
If a rule depends entirely on:
Course status
Course lessons
then Course is probably a good owner।
If it needs:
Payment gateway
Database
Another aggregate
Current user permissions
an application/service layer may be more appropriate।
Comments vs Expressive Code
Weak:
// Check if course has at least one lesson
if (
course.getLessons()
.size()
> 0
) {
}
The comment repeats the code।
Better:
if (
course.hasLessons()
) {
}
No comment needed।
Comments Should Explain Why
Useful comment:
// Keep legacy course codes uppercase because external exports
// use the code as a case-sensitive integration identifier.
This explains a non-obvious reason।
Less useful:
// Convert course code to uppercase
code =
code.toUpperCase();
The code already says that।
Avoid Commenting Bad Names Instead of Fixing Them
Weak:
// Calculates whether course is ready to publish
boolean x() {
}
Better:
boolean isReadyToPublish() {
}
Comments Can Become Stale
Code changes, comment does not:
// Maximum 10 lessons
if (
lessons.size()
>= 50
) {
}
Now the comment is misleading।
Executable code is the source of truth।
Use comments for reasoning that code cannot express well।
Avoid Magic Values
Weak:
if (
lessons.size()
>= 50
) {
}
If 50 has domain meaning:
private static final int MAX_LESSONS =
50;
Then:
if (
lessons.size()
>= MAX_LESSONS
) {
}
Intent becomes clearer।
Avoid Clever One-Liners
Weak:
return c != null && c.getS() == DRAFT && !c.getL().isEmpty();
Better:
return course != null
&& course.isDraft()
&& course.hasLessons();
Readable code wins over compressed code।
Long Conditional Expressions
Suppose:
if (
course.getStatus()
== CourseStatus.DRAFT
&& !course.getLessons()
.isEmpty()
&& course.getTitle()
.length()
>= 10
&& course.getPriceInPaisa()
>= 0
) {
}
Extract:
if (
course.isReadyForReview()
) {
}
Then put the rule where it belongs।
Avoid Negative Logic When Positive Reads Better
Harder:
if (
!course.isNotPublished()
) {
}
Better:
if (
course.isPublished()
) {
}
Double negatives increase cognitive load।
Temporary Variables Can Improve Readability
You do not need to chain everything।
Dense:
return repository.findAll()
.stream()
.filter(c -> c.getStatus() == PUBLISHED)
.filter(c -> c.getLessons().size() > 3)
.map(Course::getTitle)
.sorted()
.toList();
Readable alternative:
List<Course> courses =
repository.findAll();
return courses.stream()
.filter(
Course::isPublished
)
.filter(
course ->
course.getLessons()
.size()
> 3
)
.map(
Course::getTitle
)
.sorted()
.toList();
Clarity is more important than minimizing local variables।
Keep Variables Close to Usage
Weak:
String normalizedTitle =
title.strip();
// 100 lines later
course.changeTitle(
normalizedTitle
);
If possible, calculate values close to where they are used।
This reduces the amount of state the reader must remember।
Avoid Reusing Variables for Different Meanings
Bad:
String value =
request.getTitle();
value =
value.strip();
value =
repository.save(
value
);
If the semantic meaning changes, use meaningful variables।
Example:
String normalizedTitle =
request.title()
.strip();
Return Early
Instead of:
public Course findPublished(
CourseCode code
) {
Course result =
null;
Course course =
repository.findByCode(
code
);
if (course != null) {
if (course.isPublished()) {
result =
course;
}
}
return result;
}
Use:
public Course findPublished(
CourseCode code
) {
Course course =
repository.findByCode(
code
);
if (course == null) {
return null;
}
if (!course.isPublished()) {
return null;
}
return course;
}
Less nesting, easier flow।
Avoid Output Parameters
Weak Java API:
void findCourse(
CourseCode code,
List<Course> result
)
where method modifies result।
Better:
Course findCourse(
CourseCode code
)
or:
List<Course> findCourses(...)
Return values communicate data flow more clearly।
Do Not Mutate Inputs Unexpectedly
Weak:
public void normalizeTitles(
List<String> titles
) {
titles.replaceAll(
String::strip
);
}
Caller may not expect its list to change।
If mutation is intended, name/document it clearly।
Otherwise return a new list:
public List<String> normalizedTitles(
List<String> titles
) {
return titles.stream()
.map(
String::strip
)
.toList();
}
Constructor Size Can Reveal Class Problems
Suppose:
public CourseService(
CourseRepository repository,
EnrollmentRepository enrollments,
PaymentGateway payments,
EmailSender emails,
MediaStorage media,
Analytics analytics,
SearchIndexer indexer,
ReportGenerator reports,
AuditLogger auditLogger
)
Nine dependencies may indicate:
CourseService owns too much
Not always—but it is a strong signal to inspect cohesion।
Dependency Count Is a Signal, Not a Rule
A coordinating application service might legitimately have several collaborators।
Do not split classes only to hit an arbitrary number।
Ask:
Do these dependencies support one coherent use case?
Class Names Should Match Responsibilities
Suppose class only publishes courses.
Instead of:
CourseService
you may eventually use:
CoursePublisher
if that improves clarity।
But do not prematurely create:
CourseCreator
CourseUpdater
CoursePublisher
CourseArchiver
when one small coherent CourseService is easier।
Balance matters।
Refactoring Example
Consider this procedural class:
public final class CourseService {
private final CourseRepository repository;
public CourseService(
CourseRepository repository
) {
this.repository =
repository;
}
public void publish(
String code
) {
if (
code == null
|| code.isBlank()
) {
throw new IllegalArgumentException(
"Code required."
);
}
Course course =
repository.findByCode(
new CourseCode(
code
)
);
if (course == null) {
throw new IllegalStateException(
"Not found."
);
}
if (
course.getStatus()
!= CourseStatus.DRAFT
) {
throw new IllegalStateException(
"Invalid state."
);
}
if (
course.getLessons()
.isEmpty()
) {
throw new IllegalStateException(
"No lessons."
);
}
for (
Lesson lesson
: course.getLessons()
) {
if (
lesson.getContent() == null
|| lesson.getContent()
.isBlank()
) {
throw new IllegalStateException(
"Incomplete lesson."
);
}
}
course.setStatus(
CourseStatus.PUBLISHED
);
repository.save(
course
);
}
}
It works, but service knows too much about course internals।
Step 1: Use Strong Input Type
Instead of:
publish(
String code
)
use:
publish(
CourseCode code
)
Now invalid course code format is handled by CourseCode।
Step 2: Extract Required Lookup
private Course requireCourse(
CourseCode code
) {
Course course =
repository.findByCode(
code
);
if (course == null) {
throw new CourseNotFoundException(
code
);
}
return course;
}
Step 3: Move Publication Rules into Course
public void publish() {
if (
status
!= CourseStatus.DRAFT
) {
throw new IllegalStateException(
"Only draft courses can be published."
);
}
if (lessons.isEmpty()) {
throw new IllegalStateException(
"Course must have at least one lesson."
);
}
boolean incompleteLesson =
lessons.stream()
.anyMatch(
lesson ->
!lesson.hasContent()
);
if (incompleteLesson) {
throw new IllegalStateException(
"Every lesson must have content."
);
}
status =
CourseStatus.PUBLISHED;
}
Step 4: Give Lesson Its Own Behavior
Instead of:
lesson.getContent() == null
|| lesson.getContent().isBlank()
use:
lesson.hasContent()
Implementation:
public boolean hasContent() {
return content != null
&& !content.isBlank();
}
Final Service
public final class CourseService {
private final CourseRepository repository;
public CourseService(
CourseRepository repository
) {
if (repository == null) {
throw new IllegalArgumentException(
"Course repository is required."
);
}
this.repository =
repository;
}
public void publish(
CourseCode code
) {
Course course =
requireCourse(
code
);
course.publish();
repository.save(
course
);
}
private Course requireCourse(
CourseCode code
) {
Course course =
repository.findByCode(
code
);
if (course == null) {
throw new CourseNotFoundException(
code
);
}
return course;
}
}
Now the method reads:
Find required course
Publish it
Save it
That is a clean application workflow।
Why the Refactored Version Is Better
The service no longer knows:
How publication eligibility is calculated
How lesson completeness is determined
Course owns course rules।
Lesson owns lesson-specific knowledge।
CourseRepository owns persistence।
Each responsibility has a clearer home।
Avoid Premature Extraction
Do not take this:
return title.strip();
and create:
TitleWhitespaceRemovalStrategy
Clean design does not mean maximum abstraction।
Create abstractions when they reduce real complexity।
Duplication vs Wrong Abstraction
Two similar lines are not always a problem।
Sometimes duplicating a small simple expression is better than forcing unrelated concepts into one generic helper।
Bad abstraction:
validateThing(
value,
1,
true,
false,
"COURSE"
);
Better:
validateCourseTitle(
title
);
or put validation inside a stronger domain type।
Refactor When You Understand the Pattern
A useful rule:
First make it work clearly.
Notice repeated concepts.
Then extract meaningful abstractions.
Do not predict every future reuse before it exists।
Method Design Checklist
Before finalizing a method, ask:
- Can I describe this method in one sentence?
- Does the name describe its effect?
- Are invalid conditions rejected early?
- Is nesting reasonable?
- Are parameters meaningful at the call site?
- Are there hidden side effects?
- Does this logic belong in another object?
- Is the method mixing infrastructure and domain behavior?
- Are comments compensating for unclear code?
Class Design Checklist
Ask:
- What concept does this class represent?
- What state does it own?
- What behavior belongs with that state?
- Does it have one coherent responsibility?
- Does it depend on too many unrelated components?
- Does it expose internal state unnecessarily?
- Are callers making decisions that the class itself should own?
- Would a smaller class improve cohesion?
- Am I splitting things without a meaningful reason?
Common Mistakes
Making Methods Tiny Without Meaning
Ten one-line methods can be harder to follow than one clear method।
Using Generic Names
process(), handle(), and manage() often hide intent।
Too Many Boolean Parameters
Call sites become cryptic।
Long Nested Conditionals
Increase cognitive load।
Putting Domain Rules in Services Only
Entities become passive data bags।
Putting Infrastructure Inside Entities
Creates wrong dependencies।
Creating God Classes
One class starts owning unrelated capabilities।
Writing Comments That Repeat the Code
Better naming usually helps more।
Abstracting Before the Pattern Exists
Creates unnecessary complexity।
Treating Line Count as the Measure of Cleanliness
Readability and cohesion matter more।
Practice Exercises
Exercise 1: Rename Methods
Improve these names:
processCourse()
doIt()
check()
manageUser()
handleData()
Choose names based on specific imagined behavior।
Exercise 2: Replace Boolean Parameter
Refactor:
void exportCourse(
Course course,
boolean full
)
into a clearer API।
Exercise 3: Guard Clauses
Refactor:
if (user != null) {
if (course != null) {
if (course.isPublished()) {
enroll(
user,
course
);
}
}
}
using guard clauses।
Exercise 4: Move Behavior
Given:
if (
order.getStatus()
== OrderStatus.DRAFT
&& order.getItems()
.size()
> 0
) {
order.setStatus(
OrderStatus.SUBMITTED
);
}
Move this rule to a meaningful method on Order।
Exercise 5: Identify God Class
A class has methods:
createCourse
deleteCourse
chargeCreditCard
sendPasswordReset
resizeAvatar
generateInvoice
Explain how you would split responsibilities।
Exercise 6: Refactor Comments
Replace:
// Check whether the course has lessons
if (
course.getLessons()
.size()
> 0
) {
}
with more expressive code।
Predict the Better Design
Question 1
Which is clearer?
course.setPublished(
true
);
or:
course.publish();
Answer
course.publish();
It expresses domain intent and can enforce publication rules।
Question 2
Which is usually clearer?
send(
user,
true
);
or:
sendUrgentNotification(
user
);
Answer
The intention-revealing method is clearer when urgency is the real distinction।
Question 3
Where should a rule based entirely on:
Course status
Course lessons
usually live?
Answer
Usually inside Course, because it owns that state and can protect its invariants।
Question 4
Should Course directly save itself to PostgreSQL?
Answer
Usually no।
Persistence is an infrastructure/repository responsibility।
Question 5
Is a 30-line method automatically bad?
Answer
No।
Its cohesion, abstraction level, readability, and responsibility matter more than an arbitrary line count।
True or False
- Every clean method must be under five lines.
- Good names reduce the need for explanatory comments.
- Guard clauses can reduce nesting.
- Boolean parameters are always wrong.
- A god class usually has low cohesion.
- Domain objects should contain every infrastructure dependency.
- Hidden side effects make APIs harder to reason about.
- Extracted methods must always be reused.
- Strong types can reduce parameter mistakes.
- More abstraction always means cleaner code.
Answers
1. False
2. True
3. True
4. False
5. True
6. False
7. True
8. False
9. True
10. False
Knowledge Check
Question 1
What does single responsibility mean?
Question 2
What is cohesion?
Question 3
Why are intention-revealing names important?
Question 4
What is a guard clause?
Question 5
Why can boolean parameters be confusing?
Question 6
When is a parameter object useful?
Question 7
What is command-query separation?
Question 8
What is a hidden side effect?
Question 9
What is a god class?
Question 10
What does "move behavior to the right object" mean?
Question 11
What should comments usually explain?
Question 12
Why should long conditions sometimes be extracted into named methods?
Question 13
Why are strong parameter types useful?
Question 14
Should every repeated line immediately become an abstraction?
Question 15
What is the main goal of clean method and class design?
Knowledge Check Answers
Answer 1
A method or class should own one coherent responsibility or reason to change rather than mixing unrelated concerns।
Answer 2
The degree to which a class's state and behavior belong to the same focused concept।
Answer 3
They let readers understand behavior without decoding implementation details।
Answer 4
An early check that rejects invalid conditions before the main flow continues।
Answer 5
At the call site, true or false often does not communicate what mode or behavior it selects।
Answer 6
When several parameters form one meaningful concept or a method call becomes hard to understand due to many related arguments।
Answer 7
A design principle that distinguishes operations that change state from operations that return information, helping make side effects predictable।
Answer 8
A state change or external action performed by a method whose name or apparent purpose does not make that effect clear।
Answer 9
A class that owns too many unrelated responsibilities and becomes a central dependency for much of the application।
Answer 10
Place rules close to the state and concept that actually owns the knowledge required to enforce them।
Answer 11
Primarily why a non-obvious decision exists, not simply what obvious code already does।
Answer 12
A meaningful method name can communicate the business rule directly and reduce cognitive load।
Answer 13
They distinguish semantically different values at compile time and reduce accidental argument swaps।
Answer 14
No. Extract only when the abstraction improves meaning, cohesion, reuse, or maintainability।
Answer 15
To make software easier to understand, safer to change, and harder to misuse।
Lesson Summary
এই lesson-এ আমরা শিখেছি:
- Clean code মানে minimum line count নয়; minimum confusion
- Methods should have clear coherent purposes
- Classes should maintain high cohesion
- Names are part of API design
- Intention-revealing methods improve readability
- Guard clauses reduce unnecessary nesting
- Method size should be judged by responsibility and abstraction level
- Long logic can often be improved through meaningful extraction
- Extracted methods do not need to be reused to provide value
- Boolean parameters can create ambiguous call sites
- Enums, stronger methods, or parameter objects can improve intent
- Strong types can prevent accidental argument swaps
- Commands and queries should have predictable behavior
- Hidden side effects make APIs harder to reason about
- God classes create low cohesion and high coupling
- Domain rules should usually live near the state they protect
- Infrastructure concerns should stay outside domain entities
- Comments should explain non-obvious reasons rather than repeat code
- Named constants improve magic-value readability
- Behavior should be moved to the object with the relevant knowledge
- Abstraction should reduce complexity, not manufacture it
- Clean Java design balances:
Readability
Cohesion
Encapsulation
Clear responsibility
Simple dependencies
Next Lesson
পরবর্তী lesson:
Dependency Design and Composition
আমরা শিখব:
- What a dependency is
- Constructor dependencies
- Dependency injection without frameworks
- Depending on interfaces at useful boundaries
- Composition over unnecessary inheritance
- Replacing hard-coded collaborators
- Testability through dependency design
- Avoiding service locators and global state
- Designing small, explicit object graphs