Lack of "contract enforcement" means a fundamental design issue
Like @docbrown I oppose the "fail fast as the best idea". The "do nothing" approach is quite feasable, and preferred, when we have a coherent design.
Your question title starts with "Breaking the contract" - but what is the contract of addScore?
There is no contract because there is a big hole in the design.
PlayerCollection
vice List<Player>
A player collection will define and enforce this elusive contract; without depending on some benevolent client code to do it for us.
public class PlayerCollection {
protected List<Player> players;
// Here, we begin to enforce the contract.
// not inheriting. Hiding List<T> methods we don't allow clients
// to do anything except what we allow.
public void addPlayer(Player newPlayer) {}
// null guard in here ==> enforcing that contract.
// if null, instantiate a new Player - if player has a default constructor.
// if duplicate, ignore - if Player overrides equals()
public void addScore(Player thisPlayer, int score) {}
// null guard here. I like do nothing, vice an exception.
// design decision time: if player not in the collection then do we add it? Your choice.
// make clients to use the API. Make them call `addPlayer`.
// I see a null player and adding zero as the same thing. So I
// say "do nothing" when null.
// adding a new player in addScore is a violation of single
// responsibility principle IMHO.
}
'Player` does its share of contract enforcement
public class Player {
public Player( parameterListHere ) {
// enforcing the contract here.
// guard for nulls.
// guard for incompatible or invalid data
// default and named parameters go a long way toward
// dynamically setting complete, valid state in the constructor.
}
// design decision: is a "raw" player object allowed? i.e. can we do without
// name, team, etc. to start with? This drives whether there is a public
// default constructor and an equals override. All "null" player objects
// would be equal to each other.
// either way, the player collection will enforce the contract,
// "contains()" just behaves differently.
public Player() {
// enforcing the contract here too!
// if there is a valid default state, a zero score for example,
// then the collection's addPlayer can enforce the contract.
}
public override Equals {}
// enforcing the contract.
// maybe name, team, jersey number, etc. define what equals means
}
The contract is already enforced when we get to the Event
class
public class Event {
private PlayerCollection players;
//private Map<Player, Integer> score;
public void addScore(Player p, int playerScore) { players.addScore(p, playerScore); }
Above is what I mean by coherent design. Classes where Single Responsibility is the primary principle in play. When composited - Event
has a PlayerCollection
that has Player
s - these classes create a synergy of "contract enforcement"