This project uses ArchUnit to enforce architectural constraints and detect design smells.
-
packages_should_be_free_of_cycles()- Verifies no cyclic dependencies between top-level packages
- Pattern:
edu.luc.etl.cs313.android.simplestopwatch.(*)
-
subpackages_should_be_free_of_cycles()- Verifies no cyclic dependencies at sub-package level
- Pattern:
edu.luc.etl.cs313.android.simplestopwatch.(*).(*)
-
model_should_only_depend_on_java_common_or_model()- Purpose: Ensures model layer is self-contained
- Allowed Dependencies:
java.*,javax.*,..common.*,..model.* - Current Violations (4):
LapRunningState.getId()→R$string.LAP_RUNNINGLapStoppedState.getId()→R$string.LAP_STOPPEDRunningState.getId()→R$string.RUNNINGStoppedState.getId()→R$string.STOPPED
-
model_should_not_depend_on_R_class()- Purpose: Explicitly forbids dependencies on Android resource class
R - Rationale: Model classes should not depend on UI-specific resources
- Current Violations (4): Same as test #3 above
- Purpose: Explicitly forbids dependencies on Android resource class
The R class uses Integer.valueOf() instead of literal integer constants:
public static final int STOPPED = Integer.valueOf(0); // Not: = 0;Reason: Java's compiler performs compile-time constant inlining for static final primitive fields with literal values. This means:
- With
int STOPPED = 0;→ Compiler replacesR.string.STOPPEDwith0in bytecode - With
int STOPPED = Integer.valueOf(0);→ Bytecode containsgetstatic R$string.STOPPED
ArchUnit operates on compiled bytecode, not source code. If constants were inlined, ArchUnit couldn't detect the dependency because it wouldn't exist in the bytecode—only in the source.
By using Integer.valueOf(), we ensure the dependency exists at runtime, making it detectable by ArchUnit.
With literal constant (inlined):
public int getId();
Code:
0: iconst_0 // Push constant 0
1: ireturn
With Integer.valueOf() (not inlined):
public int getId();
Code:
0: getstatic #37 // Field R$string.STOPPED:I
3: ireturn
# Run all architecture tests
./gradlew test --tests "*.PackageDependencyTest"
# Run specific test
./gradlew test --tests "*.PackageDependencyTest.model_should_not_depend_on_R_class"
# Run with detailed violation output
./gradlew test --tests "*.PackageDependencyTest" --infoTo eliminate the architectural violations, the R class constants should be moved to the common package or the state classes should use a different mechanism for state identification that doesn't depend on UI resources.
For example:
- Move constants to
Constants.javain thecommonpackage - Have states return their own type/class as identification
- Use an enum for state identification