E11025: class has too many service and data fields - split into component + data class
What went wrong
Your class has 3+ service fields AND 3+ data fields. This is the classic "Blob" or "God Class" anti-pattern - the class is trying to do too much.
This pattern indicates MIXED RESPONSIBILITIES:
- Holding significant state (data responsibility)
- Coordinating multiple services (component responsibility)
The Single Responsibility Principle states: "A class should have only one reason to change." Your class has at least two distinct reasons to change.
SEPARATION GUIDANCE - Ask these questions for each field:
DATA FIELDS (should go to a data class):
- Does it represent state that persists between method calls?
- Is it passed to/from external systems?
- Would it make sense in a record?
SERVICE FIELDS (should go to a component):
- Does it perform operations (validate, transform, notify)?
- Is it stateless (same input -> same output)?
- Would it benefit from dependency injection?
Worked examples
Compilable examples demonstrating this error and the correct fix:
- ek9 -q 861 (Why does EK9 reject classes with too many service and dat...)
How to fix it
- SPLIT (recommended)
- Extract data fields into a separate data class
- Create a component for service coordination
- Pass data to component methods instead of storing it
- REDUCE SERVICES (if over-designed)
- Combine related services into a single service
- Question whether all services are truly needed
- Example: validator + sanitizer -> inputProcessor
- REDUCE DATA (if data should be parameters)
- Pass data as method parameters instead of storing
- Keep only truly persistent state as fields
Transform
The [EXAMPLE] section below shows an OrderProcessor class holding three services and three data fields, then the split: the order's data in an 'Order' record, and an 'OrderProcessor' component holding the services and taking the Order as a parameter. Reducing either side below three also clears the error - combining two services into one, or passing a value that was stored (the shipping address, say) as a parameter.
When not to split
- If the 6 fields are genuinely cohesive (all needed for one operation)
- If splitting would require passing many parameters between pieces
- If the "component" would have only one method
In these cases, the threshold may be highlighting over-engineering. Consider REMOVING services rather than adding more structure.
See: https://ek9.io/composition.html "Breaking up classes into smaller implementations creates many classes to be composed." The key is finding the RIGHT blend. Small, well-defined pieces are more reliable, testable, and reusable. But don't split just to meet a threshold - split when it improves clarity.
Why this matters
- Each class has a single, clear responsibility
- Data class is simple, predictable, easy to test
- Component can be injected and configured
- Changes to data don't affect service coordination
- Changes to services don't affect data structure
THRESHOLD: >= 3 service fields AND >= 3 data fields
Why this threshold
- 2 services + 4 data = OK (data class with helpers)
- 4 services + 2 data = OK (stateful coordinator)
- 3 services + 3 data = ERROR (neither dominates, mixed)
Research basis
- Brown et al.: 'AntiPatterns' (1998) - The Blob pattern
- Marinescu, R.: IEEE ICSM 2004 - God Class detection
- Riel, A.J.: 'OO Design Heuristics' (1996)
- Chidamber & Kemerer: CK Metrics Suite - LCOM
- Basili, Briand, Melo: IEEE TSE 1996 - OO metrics validation
Examples
Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.
Fails with E11025: class has too many service and data fields - split into component + data class
#!ek9 defines module error.e11025.bad defines record Order id as String: String() item as String: String() default operator ? Amount amount as Float: 0.0 currency as String: "USD" default operator ? Address street as String: String() city as String: String() default operator ? defines trait ValidatorService validate() as abstract PricingService calculatePrice() as abstract ShippingService ship() as abstract NotificationService notify() as abstract defines class ValidatorImpl with trait of ValidatorService override validate() require true PricerImpl with trait of PricingService override calculatePrice() require true ShipperImpl with trait of ShippingService override ship() require true NotifierImpl with trait of NotificationService override notify() require true defines class OrderProcessor //3 service fields validator as ValidatorService: ValidatorImpl() pricer as PricingService: PricerImpl() shipper as ShippingService: ShipperImpl() //3 data fields currentOrder as Order: Order() totalAmount as Amount: Amount() shippingAddress as Address: Address() process() validator.validate() default operator ? defines class MassiveBlob //4 service fields validator as ValidatorService: ValidatorImpl() pricer as PricingService: PricerImpl() shipper as ShippingService: ShipperImpl() notifier as NotificationService: NotifierImpl() //4 data fields order1 as Order: Order() order2 as Order: Order() amount as Amount: Amount() destination as Address: Address() doWork() validator.validate() default operator ? defines class //OK: 2 services + 4 data = under threshold DataClassWithHelpers validator as ValidatorService: ValidatorImpl() pricer as PricingService: PricerImpl() order1 as Order: Order() order2 as Order: Order() amount as Amount: Amount() destination as Address: Address() doWork() validator.validate() default operator ? defines class //OK: 4 services + 2 data = under threshold StatefulCoordinator validator as ValidatorService: ValidatorImpl() pricer as PricingService: PricerImpl() shipper as ShippingService: ShipperImpl() notifier as NotificationService: NotifierImpl() currentOrder as Order: Order() amount as Amount: Amount() doWork() validator.validate() default operator ? defines class //OK: 3 services + 2 data = under threshold JustUnderThreshold validator as ValidatorService: ValidatorImpl() pricer as PricingService: PricerImpl() shipper as ShippingService: ShipperImpl() order as Order: Order() amount as Amount: Amount() doWork() validator.validate() default operator ? defines class //OK: 2 services + 3 data = under threshold OtherBoundary validator as ValidatorService: ValidatorImpl() pricer as PricingService: PricerImpl() order as Order: Order() amount as Amount: Amount() destination as Address: Address() doWork() validator.validate() default operator ?
Fixed: Keep the services in a component and the data in a record
#!ek9 defines module error.e11025.good defines record Order id as String: String() item as String: String() default operator ? defines trait ValidatorService validate() as abstract PricingService calculatePrice() as abstract defines class ValidatorImpl with trait of ValidatorService override validate() stdout <- Stdout() stdout.println("valid") PricerImpl with trait of PricingService override calculatePrice() stdout <- Stdout() stdout.println("priced") defines component OrderProcessor validator as ValidatorService: ValidatorImpl() pricer as PricingService: PricerImpl() process() -> order as Order require order? validator.validate() pricer.calculatePrice() default operator ?
Questions and answers
These Q&A entries show E11025 and its fix: