book from 1975
cost total = cost develop + cost maintain
cost maintain = cost understand + cost change +
cost test + cost deploy
Degree of interdependence between software modules.
Kent Beck
“Coupling between A and B is defined as "if I change element A I also have to change element B"”
Degree to which the elements of a module belong together.
Kent Beck
“Cohesion of an element A is defined as "if I change subelement A.1, do I have to change all the other subelements A.2-n?". So, cohesion is coupling within an element”
book from 1995
Connascence between 2 software components A and B means either
That you can postulate some change to A that would require B to be changed (or at least carefully checked) in order to preserve overall correcteness, or
That you can postulate some change that would require both A and B to be changed together in order to preserve overall correcteness.
Connasnence is a vocabulary to talk about coupling
Name, Type, Meaning, Algorithm, Position
Execution, Timing, Value, Identity
In the same function?
in the same class?
in the same package/namespace?
in the same bounded context?
in the same application?
Static
Dynamic
public class Test {
...
var ob1 = new Ob1();
ob1.doSomething();
...
}
public class Ob1 {
public void doSomething(){ //code }
}
public class User {
public string Name { get; set; }
}
...
var user = connection.Query<User>("select name from users where id=1");
...
Console.WriteLine("Username is: " + user.name);
when multiple components must agree on the type of an entity
public class Credentials {
...
}
public class LoginService {
public bool login(Credentials credentials) {
...
}
}
loginService.login(new Credentials(...));
In dynamically typed langauages can be more dangerous
def calculate_age(birth_day, birth_month, birth_year):
...
// How call this method?
calculate_age(1, 9, 1984)
calculate_age(1, 9, 84)
calculate_age('1', '9', '1984')
calculate_age('1', 'September', '1984')
when multiple components must agree on the meaning of particular values
// Java comparable
a.compareTo(b)
// return 0 if are equal
// return positive number if a > b
// return negative number if b > a
public User getUser(string userId) { ... }
User user = obj.getUser("id");
if (user != null) {
// do something
}
// refactor to convert from CoMeaning to CoType
public Maybe<User> getUser(string userId) { ... }
Maybe<User> user = obj.getUser("id");
if (user.exits()) {
// do something
}
// when the server receives the book request
public class BookTaxi {
public void Book(Date reservationDate,...) {
if (!isInTheNextFiveDays(date)) {
return throw BookException(...)
}
}
}
// in client code, js for example
function BookTaxi(date,...) {
if (!isInTheNextFiveDays(date)) {
// show a message to the user
}
}
when multiple components must agree on a particular algorithm
def write_data_to_cache(data_string):
with open('/path/to/cache', 'wb') as cache_file:
cache_file.write(data_string.encode('utf8'))
def read_data_from_cache():
with open('/path/to/cache', 'rb') as cache_file:
return cache_file.read().decode('utf8')
// using a object that encapsulates the encoding/decoding algorithm
def write_data_to_cache(data_string):
with open('/path/to/cache', 'wb') as cache_file:
cache_file.write(self.Algorithm.encode(data_string)
def read_data_from_cache(codificationAlgorithm):
with open('/path/to/cache', 'rb') as cache_file:
return self.Algorithm.decode(cache_file.read())
// connascence of position
function serverCall(url, method, data, okCallback, errorCallback) {
....
}
serverCall("/endpoint", "post", {foo: 'foo'}, ()=> {
// do something
}, ()=> {
// handle error
});
when multiple entities must agree on the order of values.
// CoP -> CoN
function serverCall({url, method, data, ok, error})
serverCall({
url: '/endpoint',
method: 'post',
data: {foo: 'foo'},
ok: ()=>{// do somehting},
error: ()=>{// handle error}
});
def calculateImageSize(url):
width = ...
height = ...
return (width, height)
(width, height) = calculateImageSize('/url/img.png')
print(width)
// using named tuples reduce from CoPosition to CoName
ImageSize = namedtuple('ImageSize', 'width height')
def calculateImageSize(url):
width = ...
height = ...
return ImageSize(width, height)
imageSize = calculateImageSize('/url/img.png')
print(imageSize.width)
Configurator configurator = new Configurator();
configurator.setModel("Ferrari F40");
List<Equipments> availableEquipments = configurator.getEquipmentsResultSet();
// refactor to reduce from CoExecution to CoType
Configurator configurator = new Configurator();
Model model = configurator.getModel("Ferrari F40");
List<Equipments> availableEquipments = model.getEquipmentsResultSet();
when the order of execution of multiple components is important.
The general workflow is something like this
Coupled with documentation!
from javadoc:
// using bootstrap modal
$(element).modal('hide')
$(element).modal('show') // Error!
// hiding a modal has an animation of aprox 500ms, if you call 'show' during
// this animation the component breaks in unexpected ways
// we need to do something like this
$(element).modal('hide')
$(element).on('hidden.bs.modal', ()=>{
$(element).modal('show') // ok
})
when the timing of the execution of multiple components is important
https://github.com/twbs/bootstrap/issues/3902
when several values must change together.
[Test]
public void total_price_for_scaned_products_with_discount() {
var productScanner = new ProductScanner();
productScanner.Scan("productA");
productScanner.Scan("productB");
productScanner.Total.Should().Be(150);
}
class ProductScanner {
private readonly HashMap<string, int> product_prices = new HashMap<string, int> {
{"productA", 100},
{"productB", 50}
}
public int Total {get; private set;}
public void Scan(String productName) {
Total += product_prices[productName];
}
}
Production code and test agree about the values of the products
private readonly HashMap<string, int> product_prices = new HashMap<string, int> {
{"productA", 100},{"productB", 50}
}
[Test]
public void total_price_for_scaned_products_with_discount() {
var productScanner = new ProductScanner(product_prices);
productScanner.Scan("productA");
productScanner.Scan("productB");
productScanner.Total.Should().Be(150);
}
class ProductScanner {
private readonly HashMap<string, int> product_prices;
public ProductScanner(HashMap<string,int> product_prices) {
this.product_prices = product_prices;
}
public int Total {get; private set;}
public void Scan(String productName) {
Total += product_prices[productName];
}
}
Reduce locality
productScanner.Total.Should()
.Be(product_prices["productA"] + product_prices["productB"]);
We can eliminate CoV with this change:
productScanner.Total.Should().Be(150);
instead of:
Now we have CoAlgorithm, less connascence strength
but... is really a best solution?
when multiple components must reference the same entity
const eventBus = EventBus();
const mainComponent = MainComponent(eventBus);
const component1 = Component1(eventBus)
const component2 = Component2(eventBus)
const component3 = Component3(eventBus)