Skip to main content

Coupling Between Object Classes (CBO)

v1.3.0
short name: CBO
effort: 15m
default threshold: 20

Coupling between object classes is a quantitative metric that measures the number of distinct classes to which a given class/mixin/enum/extension type is coupled. Two classes are considered coupled if one class uses methods, instance variables or the type (e.g. as a parameter or return type) of the other class.

High CBO indicates a fragile class that is affected by any change in any of the coupled classes.

Additional resources: Chidamber-Kemerer metrics paper.

Why Be Cautious

High coupling between object classes can lead to:

  • Maintainability Challenges: High CBO leads to "Ripple Effects"—changing one class breaks five others.
  • Difficulty in Testing: High CBO means you need to mock many classes to unit test a single one.
  • Prop-Drilling Indication: If a widget has a high CBO because it receives 10 different domain models in its constructor, it is suffering from Prop-Drilling.

How to Address High Coupling Between Object Classes?

When a class relies on several related classes, consider encapsulating those classes within a module or service. This approach minimizes the number of classes a single class is coupled to by grouping related functionality.

When dealing with widgets, if a Widget is coupled to 10 models because it has to pass them down the tree, use a State Management solution (Riverpod, Provider, or Bloc) instead to "inject" dependencies only where they are used. This will lead to intermediate widgets losing all the coupling to those models, drastically lowering their CBO and making them more reusable.

Config Example

analysis_options.yaml
dcm:
metrics:
coupling-between-object-classes:
threshold: 12

To set multiple threshold or other common config options, refer to the Configuring Metrics page.

Example

info

To view what contributes to the metric value, generate an HTML report.

❌ Bad: High Coupling Between Object Classes

// CBO: 10
class FileUploadCoordinator {
final CloudStorage storage;
final UserQuota quota;
final EncryptionService crypto;

FileUploadCoordinator(this.storage, this.quota, this.crypto);

Future<void> startUpload(LocalFile file, ProgressUpdate progress) async {
if (!NetworkMonitor.isWifi()) return;

if (quota.hasSpace(1024)) {
try {
final raw = FileChunk([0, 1, 2]);
final FileChunk small = CompressionTool.compress(raw);
final UploadResult res = await storage.push(small);

if (res.isDone) {
print("Progress: ${progress.percent}");
}
} catch (e) {
ErrorReporter.capture(e);
}
}
}
}

✅ Good: Low Coupling Between Object Classes

// CBO: 3
class FileUploadCoordinator {
final UploadPipeline pipeline;
final ErrorReporter reporter;

FileUploadCoordinator(this.pipeline, this.reporter);

Future<void> startUpload(FileSource source) async {
if (!pipeline.isReady) return;

try {
final success = await pipeline.executeTransfer(source.data);

if (success) {
print("Progress: ${source.progress}");
}
} catch (e) {
reporter.capture(e);
}
}
}

// CBO: 0
abstract class UploadPipeline {
Future<bool> executeTransfer(List<int> bytes);
bool get isReady;
}

// CBO: 0
class FileSource {
final List<int> data;
final double progress;
FileSource(this.data, this.progress);
}

// CBO: 0
abstract class ErrorReporter {
void capture(Exception e);
}