Code Smell 139 - Business Code in the User Interface
Validations should be on the interface, or not?
TL;DR: Always create correct objects in your back-ends. UIs are accidental.
Problems
Security problems
Code Duplication
Testability
Extensibility to APIs, micro-services, etc.
Anemic and mutable objects
Solutions
- Move your validations to the back-end.
Context
Code Duplication is a warning for premature optimization.
Building a system with UI validations might evolve to an API or external component consumption.
We need to validate objects on the back-end and send good validation errors to client components.
Sample Code
Wrong
<script type="text/javascript">
function checkForm(form)
{
if(form.username.value == "") {
alert("Error: Username cannot be blank!");
form.username.focus();
return false;
}
re = /^\w+$/;
if(!re.test(form.username.value)) {
alert("Error: Username must contain only letters, numbers and underscores!");
form.username.focus();
return false;
}
if(form.pwd1.value != "" && form.pwd1.value == form.pwd2.value) {
if(form.pwd1.value.length < 8) {
alert("Error: Password must contain at least eight characters!");
form.pwd1.focus();
return false;
}
if(form.pwd1.value == form.username.value) {
alert("Error: Password must be different from Username!");
form.pwd1.focus();
return false;
}
re = /[0-9]/;
if(!re.test(form.pwd1.value)) {
alert("Error: password must contain at least one number (0-9)!");
form.pwd1.focus();
return false;
}
re = /[a-z]/;
if(!re.test(form.pwd1.value)) {
alert("Error: password must contain at least one lowercase letter (a-z)!");
form.pwd1.focus();
return false;
}
re = /[A-Z]/;
if(!re.test(form.pwd1.value)) {
alert("Error: password must contain at least one uppercase letter (A-Z)!");
form.pwd1.focus();
return false;
}
} else {
alert("Error: Please check that you've entered and confirmed your password!");
form.pwd1.focus();
return false;
}
alert("You entered a valid password: " + form.pwd1.value);
return true;
}
</script>
<form ... onsubmit="return checkForm(this);">
<p>Username: <input type="text" name="username"></p>
<p>Password: <input type="password" name="pwd1"></p>
<p>Confirm Password: <input type="password" name="pwd2"></p>
<p><input type="submit"></p>
</form>
Right
<script type="text/javascript">
// send a post to a backend
// backend has domain rules
// backend has test coverage and richmodels
// it is more difficult to inject code in a backend
// Validations will evolve on our backend
// Business rules and validations are shared with every consumer
// UI / REST / Tests / Microservices ... etc. etc.
// No duplicated code
function checkForm(form)
{
const url = "https://<hostname/login";
const data = {
};
const other_params = {
headers : { "content-type" : "application/json; charset=UTF-8" },
body : data,
method : "POST",
mode : "cors"
};
fetch(url, other_params)
.then(function(response) {
if (response.ok) {
return response.json();
} else {
throw new Error("Could not reach the API: " + response.statusText);
}
}).then(function(data) {
document.getElementById("message").innerHTML = data.encoded;
}).catch(function(error) {
document.getElementById("message").innerHTML = error.message;
});
return true;
}
</script>
Detection
[X] Semi-Automatic
We can detect some behavior patterns in our UI code
Tags
- Mutability
Conclusion
Use TDD.
You will put all your business logic behavior on your domain objects.
Relations
Code Smell 97 - Error Messages Without Empathy
Maxi Contieri ・ Oct 27 '21
Code Smell 01 - Anemic Models
Maxi Contieri ・ Oct 20 '20
Code Smell 90 - Implementative Callback Events
Maxi Contieri ・ Oct 7 '21
More Info
Credits
Photo by Lenin Estrada on Unsplash
I think another good principle is separating presentation or user interface (UI) from the real essence of what your app is about. By following that principle I have gotten lucky with changes time and time again. So I think that's a good principle to follow.
Martin Fowler
Software Engineering Great Quotes
Maxi Contieri ・ Dec 28 '20
This article is part of the CodeSmell Series.