When working on applications or domain models, I often ponder the most effective approach to handling incomplete or optional data. Typed languages like TypeScript and C# offer the advantage of defining models with strict types. While this can be beneficial by enforcing constraints on the data, it becomes challenging when dealing with incomplete real-life data that does not align with these constraints.
Consider a basic data model for a sample application (using TypeScript for the frontend and some backend) where an entity named Project has an id, a name, and an optional description. This data is fetched from the backend.
The frontend defines the data model using interfaces:
export interface IProject {
id: number;
name: string;
description: string;
}
The data is fetched from the backend using the following approach:
export class ProjectService {
public getProject(projectId: number): Observable<IProject> {
const url = 'http://server/api/project/' + projectId;
return this.httpClient.get<IProject>(url);
}
}
For instance, here is a sample response for a project that includes a description.
{
"id": 1
"name": "My first project",
"description": "My first project is just a demo"
}
In the frontend application, we display the retrieved data. Let's say we want to show the first 10 characters of the project description.
alert(project.description.substr(0,10));
Everything works smoothly so far.
However, now imagine a scenario where the user creates "Project two" without providing a description. In this case, the backend can respond with:
{
"id": 2
"name": "Project two"
}
or
{
"id": 2
"name": "Project two",
"description" : null
}
As a result, we encounter a null-reference exception in the frontend: "Cannot read property 'substr' of null". While we can add an if statement to check for null descriptions, this approach would clutter the codebase with numerous checks, potentially masking bugs instead of preventing them.
One possible solution could be to always return a description, even if it is empty when not provided.
{
"id": 2
"name": "Project two",
"description": ""
}
By implementing this change, the frontend no longer needs null checks for descriptions. However, it becomes challenging to differentiate between an intentionally empty description and one that has not been filled in yet.
How should we address these challenges effectively? The example above serves as an illustration, but the issue is applicable in various scenarios within typed languages that struggle with fulfilling a rigid type contract/interface.