DEV Community

Cover image for The “Tell, don’t ask” principle
le0nidas
le0nidas

Posted on • Originally published at le0nidas.gr on

3

The “Tell, don’t ask” principle

This is one of my favorite principles because it is easy to spot when violated and because it helps in having proper API surfaces when applied.

Tell, don’t ask

In essence this principle proposes that instead of asking from an instance for its values in order to decide how the same instance will execute something, just tell the instance to execute it. It knows its own state, it can make its own decisions!

Asking

By asking we actually refer to accessing many of a class’s properties. For example:

// somewhere in the code
if (task.status == Status.NotStarted && task.createdAt < LocalDate.of(2021, 1, 14) && task.subscribers.isEmpty()) {
task.close()
}
// which might have led to this API
class Task(
initialStatus: Status,
val createdAt: LocalDate
) {
var status: Status = initialStatus
private set
private val innerSubscribers = mutableListOf<Subscriber>()
val subscribers: List<Subscriber> = innerSubscribers
fun add(subscriber: Subscriber) {
innerSubscribers.add(subscriber)
}
fun close() {
status = Status.Resolved
}
}

in the code above we ask the task to provide the values of three of its properties in order to decide if we are going to close it or not.

In the process we (a) might had to expose those properties just for this code (creation date and subscribers could be private) and (b) inevitably leaked business logic that involves a task (when a task is eligible for closing).

Telling

Lets change the code in order to tell the task to close itself if possible:

// somewhere in the code
task.closeIfUnclaimed(createdBefore = LocalDate.of(2021, 1, 14))
// task's better API
class Task(
initialStatus: Status,
private val createdAt: LocalDate
) {
var status: Status = initialStatus
private set
private val subscribers = mutableListOf<Subscriber>()
fun add(subscriber: Subscriber) {
subscribers.add(subscriber)
}
fun closeIfUnclaimed(createdBefore: LocalDate): Boolean {
if (status == Status.NotStarted && createdAt < createdBefore && subscribers.isEmpty()) {
status = Resolved
return true
}
return false
}
}

There are two things that we gain from this change:

  1. we moved the “closing” logic in the Task itself which allows us to contain future logic alterations in just one place.
  2. the class’s API exposes only the necessary helping us to avoid accidental couplings.

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay