I wrote a little app to manage an arbitrary series of tasks (e.g., call a SQL sproc and capture out-vars, run another app, run an SSIS package) with dependencies between tasks. Each task has a status (waiting to start, failed and waiting to retry, failed and abandoned, succeeded, etc.). Internally, each Task
object has a field of the TaskStatus
enum type. There’s a TaskManager
object which starts tasks and monitors their successes and failures, and a Dashboard
WinForm which displays progress.
Should I expose each Task
‘s status as an enum throughout the app, or only to TaskManager
, or always expose only the string value? My approach has been to expose the status as an enum, but this means that not only TaskManager
but also Dashboard
has a dependency on the possible values for TaskStatus
.
I almost posted this question on code review, but on balance I think it fits better here, sense it’s mostly a matter of principle.
Dashboard
only interacts with Task.Status
in a two places. First, its value is displayed in a DataGridView
which summarizes all tasks, and this really should use a text value instead, if only to allow for prettier values. Second, status values are compared when the Status column is sorted, and obviously this will work either way.
TaskManager
is the iffy part. For example, a TaskManager
has props NumTasksRunning
, NumTasksComplete
, and NumTasksWaiting
. Originally, with Task
s exposing their statuses directly, I put this logic in TaskManager
:
public int NumTasksRunning { get { return _tasks.Count(t => t.IsEnabled && (t.Status == TaskStatus.Running || t.Status == TaskStatus.Aborting || t.Status == TaskStatus.FailedAwaitingRetry)); } }
public int NumTasksComplete { get { return _tasks.Count(t => t.IsEnabled && (t.Status == TaskStatus.Succeeded || t.Status == TaskStatus.FailedAbandoned)); } }
public int NumTasksWaiting { get { return _tasks.Count(t => t.IsEnabled && (t.Status == TaskStatus.Waiting)); } }
I could move this logic to properties in Task
, protecting TaskManager
from needing to know about the enum:
public bool IsRunning { get { return _status == TaskStatus.Running || _status == TaskStatus.Aborting || _status == TaskStatus.FailedAwaitingRetry; } }
public bool IsComplete { get { return _status == TaskStatus.Succeeded || _status == TaskStatus.FailedAbandoned; } }
public bool IsWaiting { get { return _status == TaskStatus.Waiting; } }
However, there are other places where the code is much simpler if TaskManager
can see tasks’ statuses directly:
public void Abort(Exception ex)
{
State = TaskManagerState.Aborting;
foreach (Task task in _tasks)
if (task.Status == TaskStatus.Running && task.CanAbort)
task.Abort(ex);
}
Of course I could add a new property to Task
, maybe called CanAbortNow
(CanAbort
means the task can be aborted in principle, separate), but this is just one of several touch points. If I end up with an “IsX” property for every possible value of TaskStatus
, have I really made the code any easier to maintain?
This app is for my personal use, so the choice really only matters in that I’d like to know the “right” way – if there is any consensus.
I’m not sure you’ll find a consensus, but:
In my opinion, it’s pretty okay to reuse the enum; better than supplying arbitrary strings anyways… If the dependency is harder than you’d like then have an intermediary adapter layer. Let the UI have its own status enum, and the glue layer adapts one enum to the other to help decouple the change impact between the UI and the task layer.
I’d tend to favor having the adapter layer, but for small or infrequently changing apps it is perhaps an unnecessary complexity.
1