Large Class
Before you begin: Classes, fields, constructors, and methods.
A Large Class has accumulated enough data, methods, and responsibilities that it is difficult to understand or change. One useful clue is that its members fall into separate groups: these methods use these fields, while those methods use entirely different fields.
The name is not a numerical rule. A short class can mix unrelated jobs, and a long class can still serve one coherent purpose. Investigate the responsibilities before deciding that a file has crossed an arbitrary line count. 1
The club manager that manages everything
Imagine a game club application. Its tournament organizer maintains scores. Its communications volunteer maintains an announcement. Both features have gradually collected inside a class named ClubManager.
This compressed example makes the two groups visible. Scores are nonnegative whole numbers; an empty scoreboard reports zero.
Original version
public sealed class ClubManager
{
private readonly List<int> scores = new List<int>();
private string announcement = "";
public void AddScore(int score)
{
scores.Add(score);
}
public int BestScore()
{
return scores.Count == 0 ? 0 : scores.Max();
}
public void SetAnnouncement(string text)
{
announcement = text;
}
public string RenderAnnouncement()
{
return "Notice: " + announcement;
}
}AddScore and BestScore use scores. SetAnnouncement and RenderAnnouncement use announcement. The score calculation never needs the announcement text.
class ClubManager:
def __init__(self):
self.scores = []
self.announcement = ""
def add_score(self, score):
self.scores.append(score)
def best_score(self):
return max(self.scores, default=0)
def set_announcement(self, text):
self.announcement = text
def render_announcement(self):
return "Notice: " + self.announcementadd_score and best_score use scores. set_announcement and render_announcement use announcement. The score calculation never needs the announcement text.
A change to tie-breaking rules and a change to announcement formatting now reopen the same class for unrelated reasons. As each feature grows, the name “manager” does less and less to explain what is inside.
Refactored version: extract the two concepts
public sealed class Scoreboard
{
private readonly List<int> scores = new List<int>();
public void Add(int score)
{
scores.Add(score);
}
public int Best()
{
return scores.Count == 0 ? 0 : scores.Max();
}
}
public sealed class Noticeboard
{
private string text = "";
public void SetText(string text)
{
this.text = text;
}
public string Render()
{
return "Notice: " + text;
}
}Scoreboard scores = new Scoreboard();
scores.Add(30);
Noticeboard notices = new Noticeboard();
notices.SetText("Friday tournament");
Console.WriteLine(scores.Best()); // 30
Console.WriteLine(notices.Render()); // Notice: Friday tournamentThe caller now holds a Scoreboard and a Noticeboard. The behavior is the same, but each class gives its related data and methods a specific name.
class Scoreboard:
def __init__(self):
self.scores = []
def add(self, score):
self.scores.append(score)
def best(self):
return max(self.scores, default=0)
class Noticeboard:
def __init__(self):
self.text = ""
def set_text(self, text):
self.text = text
def render(self):
return "Notice: " + self.textscores = Scoreboard()
scores.add(30)
notices = Noticeboard()
notices.set_text("Friday tournament")
print(scores.best()) # 30
print(notices.render()) # Notice: Friday tournamentThe caller now holds a Scoreboard and a Noticeboard. The behavior is the same, but each class gives its related data and methods a specific name.
This is an Extract Class refactoring. In an established public API, a temporary forwarding method can preserve old calls while their consumers migrate. The example shows the updated caller explicitly rather than implying that its method names remained unchanged. 2
What not to split
A screen may legitimately coordinate both objects. That does not mean it should absorb their score arithmetic and text-formatting rules. Coordination and ownership of every detail are different things.
Conversely, splitting a class into several files does not help if the pieces still inspect one another’s internal fields and must change together for every request. Look for a separation that lets a programmer understand and test a real responsibility independently.
The goal is not a collection of tiny classes. It is a collection of classes whose names tell the truth about their work.
Sources
- Martin Fowler — Code Smell, on investigating a visible clue rather than treating it as proof.
- Martin Fowler — Extract Class. The club example is original.