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 Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)