Ender Dincer
Mar 9, 2023
Clean Code Principles
In this article, I will share principles that I, personally trust and follow to achieve reliable, readable, maintable and extendable Java/Kotlin code. I have learned most of them the hard way and have never stopped following them since. For these principles to work, they should be accepted by all developers in a development team and following them should be mandatory. Pull requests that are not following these principles should be rejected with a reference to the appropriate principle. In other words, this should be treated as a contract.
At first sight, the list can look overwhelming, and yes following all will take some time, but I promise it will save more time than it takes in the long run. Finally, I’m not claiming all principles in the article are perfect or suitable for all application types, there are applications where some principles can’t be applied.
For each type below, there are naming conventions accepted by the industry. The naming convention for variables that are final, static, non-static or non-final is camel case.
Copy
public double price = 2.0;
protected final String firstName = "Bob";
private static int counter = 0;
Copy
1Output: -
Copy
val price = 2.0;
protected val firstName = "Bob";
private val counter = 0;
Copy
1Output: -
Copy
public static final String EMPTY_STRING = "";
private static final int NUM_OF_DAYS_IN_A_WEEK = 7;
Copy
1Output: -
Copy
object Constants {
const val EMPTY_STRING = ""
private const val NUM_OF_DAYS_IN_A_WEEK = 7
}
Copy
1Output: -
Copy
public void processFile(File file) {}
private int getAge() {}
static String mergeStrings(String string1, String string2) {}
Copy
1Output: -
Copy
fun processFile(file: File?) {}
private fun getAge(): Int {}
fun mergeStrings(string1: String?, string2: String?): String? {}
Copy
1Output: -
Copy
public class RouteManager {}
public sealed interface Executable permits Program {}
private static class Employee {}
public abstract class AbstractFileProcessor {}
Copy
1Output: -
Copy
class RouteManager
interface Executable
private class Employee
abstract class AbstractFileProcessor
Copy
1Output: -
The names can follow all the naming conventions correctly but if they don’t have meaningful names then they will be a maintenance problem. The names given to variables, classes and methods should clearly state the purpose. All developers should think the same thing after reading a variable name or they shouldn’t be guessing the intention of a class. In addition, the name should consist of domain terminology to the extent that non-technical members of the team should be able to understand the flow by taking a quick look at the code.
One rule that applies to all below is, of course, not to use single letter names. The simplest example in which naming conventions are applied but the name has no meaning, revealing no objective.
Variable names should match the type of the variable. If the type of the variable is primitive (except boolean) or their corresponding objects then it should be a noun or the phrase shouldn't contain a verb.
Copy
// good example
final long remainingAmountInKg = 22L;
// bad example
String getAccountName = "Bob's account"
Copy
1Output: -
Copy
// good example
val remainingAmountInKg = 22L;
// bad example
var getAccountName = "Bob's account"
Copy
1Output: -
Copy
boolean hasWings = false;
boolean isCleanCode = true;
Boolean shouldRestart = null;
boolean canFly = false;
Copy
1Output: -
Copy
var hasWings = false
val isCleanCode = true
var shouldRestart: Boolean? = null
var canFly = false
Copy
1Output: -
Copy
final Function<Integer, String> convertIntegerToString = ((Integer num) -> String.valueOf(num));
final Function<String, String> trimString = String::trim;
List<String> stringList = Stream.of(1, 2, 3)
.map(convertToString)
.map(trimString)
.toList();
Copy
1Output: -
Copy
val convertIntegerToString = { num: Int -> num.toString() }
val trimString = { string: String -> string.trim() }
val stringList = sequenceOf(1, 2, 3)
.map { convertIntegerToString(it) }
.map { trimString(it) }
.toList()
Copy
1Output: -
Copy
public class Student {}
private static class ServerResponse {}
public class TemporaryConfiguration {}
Copy
1Output: -
Copy
Copy
1Output: -
Copy
// good examples
public class CsvProcessor {}
public class ReportGenerator {}
public class AccountValidationService {}
public class StringHelper {}
public class EntityManager {}
// bad examples
public class CsvProcessing {}
public class ReportGen {}
public class AccountValidation {}
public class StringUtil {}
Copy
1Output: -
Copy
// good examples
class CsvProcessor
class ReportGenerator
class AccountValidationService
class StringHelper
class EntityManager
// bad examples
class CsvProcessing
class ReportGen
class AccountValidation
class StringUtil
Copy
1Output: -
Copy
public void sendEmail(Email email) {}
static double calculate(int num1, int num2, char operator) {}
Copy
1Output: -
Copy
fun sendEmail(email: Email?) {}
fun calculate(num1: Int, num2: Int, operator: Char): Double {}
Copy
1Output: -
Modern applications consist of many abstraction layers. Each layer should have its own terminology and each layer's low level details should not be exposed to other layers. A typical application using Spring framework consist of a presentation/communication layer (REST APIs, Kafka Producers/Consumers), a service layer, a domain layer, a data access layer, and an infrastructure/configuration layer - mostly abstracted by the framework. For example, keywords like Singleton, Prototype, Bean shouldn't reach other layers and stay in the infrastructure layer. There are some implementation details that should not be used at all, in any layer. I have seen codebases where interface names have the "I" suffix or prefix and implementing classes have the "Impl" suffix like below. These names don't tell anything about the purpose but only show inheritance relationships.
Copy
// bad examples
public interface EventHandlerI {}
public interface IEventRouter {}
public class EventHandlerImpl implements EventHandlerI {}
// good examples
public interface EventHandler {}
public class OrderEventHandler implements EventHandler {}
public class BasketEventHandler implements EventHandler {}
Copy
1Output: -
Copy
// bad examples
interface EventHandlerI
interface IEventRouter
class EventHandlerImpl : EventHandlerI
// good examples
interface EventHandler
class OrderEventHandler : EventHandler
class BasketEventHandler : EventHandler
Copy
1Output: -
Copy
public class DefaultEventHandler implements EventHandler {}
public class FallbackEventHandler implements EventHandler {}
Copy
1Output: -
Copy
class DefaultEventHandler : EventHandler
class FallbackEventHandler : EventHandler
Copy
1Output: -
Copy
// bad examples
public class StudentEntity {}
public class CustomerModel {}
public class EntityAccount {}
public class RepoBookEntity {}
// good examples
public class Student {}
public class Customer {}
public class Account {}
public class Book {}
Copy
1Output: -
Copy
// bad examples
class StudentEntity
class CustomerModel
class EntityAccount
class RepoBookEntity
// good examples
class Student
class Customer
class Account
class Book
Copy
1Output: -
Copy
public Employee {
// ...
public static EmployeeBuilder {
// ...
}
}
Copy
1Output: -
Copy
// Named constructor arguments are used instead of the builder pattern Kotlin.
Copy
1Output: -
Another example is DTOs. DTOs, inbound and outbound, are essential to protect the data contract between services. Using suffixes like "Request", "Response" or "Dto" is fine, as long as they are not exposed to lower layers of the application like domain or infrastructure. Why is a Dto suffix fine but not an Entity suffix? Because there is no such thing as a student entity or a student model in real life but a student DTO is a technical abstraction. It does precisely what it says, it is a data transfer object. Naming DTOs this way also helps developers to be more cautious about modifying DTOs as introducing a breaking change to the data contract is very easy.
Copy
public class GetUserRequest {}
public class CreateUserRequest {}
public class UserDto {}
Copy
1Output: -
Copy
data class GetUserRequest()
data class CreateUserRequest()
data class UserDto()
Copy
1Output: -
Suffixes like data, info and details add no value to the names, therefore they should be avoided. They are too generic. What's difference between a User, UserData and UserInfo? What does CustomerDetails tell? What kind of details does it cover? Address, account or contact details? These kind of suffixes that does not have any value should not be used in names. Not even for object composition.
Using the right name for design patterns is crucial too. I have seen names that do not match the design pattern used at all. An example for the wrong name use can be using adaptor suffixes for dto converters like UserEntityToUserDtoAdaptor. Each time someone new joins to the team, existing members will have to explain the "different" naming convention used in the codebase. Instead, names known and accepted by the community should be used.
Abbreviations should be treated as new words as well, for example CustomerDao is correct but CustomerDAO is not according to this rule.
This is my preferred way but if the team prefers keeping the abbreviations all uppercase that is fine too as long as only one is allowed in the team, not both.
Copy
// option 1
public class CustomerDto {}
public class CustomerDtoMapper {}
final String callerId = "123";
// option 2
public class CustomerDTO {}
public class CustomerDTOMapper {}
final String callerID = "123";
Copy
1Output: -
Copy
// option 1
data class CustomerDto()
class CustomerDtoMapper {}
val callerId = "123"
// option 2
data class CustomerDTO()
class CustomerDTOMapper {}
val callerID = "123"
Copy
1Output: -
The fewer parameters a method has, the more readable and maintainable it is.
I will start with an example where some parameters of the function can be derived from other parameters.
Copy
public record JobApplication(
Applicant applicant,
Instant appliedOn,
String status,
boolean isCaptchaFailed
) {}
public record Applicant(
String firstName,
String lastName,
String phoneNumber,
String resumeUrl
) {}
Copy
1Output: -
Copy
public class JobApplication{
private Applicant applicant;
private Instant appliedOn;
private String status;
private boolean isCaptchaFailed;
// constructor, getters, and setters
}
public class Applicant{
private String firstName;
private String lastName;
private String phoneNumber;
private String resumeUrl;
// constructor, getters, and setters
}
Copy
1Output: -
Copy
data class JobApplication(
val applicant: Applicant,
val appliedOn: Instant,
val status: String,
val isCaptchaFailed: Boolean
)
data class Applicant(
val firstName: String,
val lastName: String,
val phoneNumber: String,
val resumeUrl: String
)
Copy
1Output: -
Copy
public static boolean validateApplication(JobApplication jobApplication, String resumeUrl, String phoneNumber) {
if (resumeUrl == null) {
return false;
}
if(phoneNumber == null) {
return false;
}
if (jobApplication.isCaptchaFailed()) {
return false;
}
return true;
}
Copy
1Output: -
Copy
fun validateApplication(jobApplication: JobApplication, resumeUrl: String?, phoneNumber: String?): Boolean {
if (resumeUrl == null) {
return false
}
if (phoneNumber == null) {
return false
}
if (jobApplication.isCaptchaFailed) {
return false
}
return true
}
Copy
1Output: -
Copy
public static boolean validateApplication(JobApplication jobApplication) {
if(jobApplication.applicant() == null){
return false;
}
if (jobApplication.applicant().resumeUrl() == null) {
return false;
}
if(jobApplication.applicant().phoneNumber() == null) {
return false;
}
if (jobApplication.isCaptchaFailed()) {
return false;
}
return true;
}
Copy
1Output: -
Copy
fun validateApplication(jobApplication: JobApplication): Boolean {
if (jobApplication.applicant() == null) {
return false
}
if (jobApplication.applicant().resumeUrl() == null) {
return false
}
if (jobApplication.applicant().phoneNumber() == null) {
return false
}
if (jobApplication.isCaptchaFailed) {
return false
}
return true
}
Copy
1Output: -
Interfaces or parent classes should be used instead of concrete child classes in input parameters. For example, instead of using ArrayList as an argument, List or Collection can be used. This way the method can accept other collection types. Another example can be accepting Map instead of TreeMap or LinkedHashMap.
Copy
// bad examples
public void deleteAddresses(ArrayList<Address> addresses) {}
public void deleteAddresses(HashSet<Address> addresses) {}
public void processAddressesWithUserIds(TreeMap<String, Address> userIdsToAddresses) {}
// good examples
public void deleteAddresses(List<Address> addresses) {}
public void deleteAddresses(Collection<Address> addresses) {}
public void processAddressesWithUserIds(Map<String, Address> userIdsToAddresses) {}
Copy
1Output: -
Copy
// bad examples
fun deleteAddresses(addresses: ArrayList<Address>) {}
fun deleteAddresses(addresses: HashSet<Address>) {}
fun processAddressesWithUserIds(userIdsToAddresses: TreeMap<String, Address>) {}
// good examples
fun deleteAddresses(addresses: List<Address>) {}
fun deleteAddresses(addresses: Collection<Address>) {}
fun processAddressesWithUserIds(userIdsToAddresses: Map<String, Address>) {}
Copy
1Output: -
This can also allow passing lambda functions if the interfaces are functional interfaces.
Use general classes/interfaces for input parameters.
Returning null from a function can have a special meaning but whenever a function can return null it introduces more work - because returned objects should be null-checked which means invitation to NPEs. There are some better options than returning null. My favourite is Optionals.
Copy
public static Integer findMaxInCollection(Collection<Integer> numbers) {
if (numbers == null) {
return null;
}
if (numbers.isEmpty()) {
return null;
}
return numbers.stream().max(Comparator.comparing(Function.identity())).get();
}
Copy
1Output: -
Copy
// Kotlin has nullable types so Optionals are redundant.
Copy
1Output: -
Copy
public static Optional<Integer> findMaxInCollection(Collection<Integer> numbers) {
if (numbers == null) {
return Optional.empty();
}
if (numbers.isEmpty()) {
return Optional.empty();
}
return numbers.stream().max(Comparator.comparing(Function.identity()));
}
Copy
1Output: -
Copy
// Kotlin has nullable types so Optionals are redundant.
Copy
1Output: -
Copy
var integers = List.of(1, 2, 3, 4, 5);
// Optional example 1
findMaxInCollection(integers).ifPresentOrElse(
this::printMaxInt,
this::logIntegerNotFound
);
// Optional example 2
var optionalMaxInt = findMaxInCollection(integers);
// here the returned object states that the value might not be present
if (optionalMaxInt.isPresent()) {
printMaxInt(optionalMaxInt.get());
} else {
logIntegerNotFound();
}
// with null checks
var maxInt = findMaxInCollection(integers);
// at this point you don't know if maxInt can be null or not
// but with optional you know you can get an empty optional as well.
if (maxInt == null) {
logIntegerNotFound();
} else {
printMaxInt(maxInt);
}
Copy
1Output: -
Copy
// Kotlin has nullable types so Optionals are redundant.
Copy
1Output: -
Before finishing Optionals, a quick disclaimer: Never use Optionals as function arguments or constructor parameters they should be used as return types only if one of the possible return types is "nothing". Also Optionals take more memory as they are objects themselves wrapping the object returned.
Another option is simply throwing an exception that explains the problem like InvalidArgumentException. In addition, if the method returns a collection then returning an empty collection is much better than returning null. Finally, returning null should be avoided, instead, optionals, exceptions or empty collections should be preferred.
Single responsibility principle applies to all units of the application. Methods, classes, even serverless functions and microservices should be responsible of doing only one thing. It is usually easy to spot functions that do multiple things. If there are different levels of abstractions, if blocks of code can be grouped and isolated, if there are multiple try-catch-finally statements, if there are nested for/while loops... Then the function does too many things. There is another one I see very often which I will show with an example as this is a bit different than others.
Copy
public boolean startServer(ServerConfigs serverConfigs){}
// ...
if (startServer(serverConfigs)) {
// ...
}
Copy
1Output: -
Copy
fun startServer(serverConfigs: ServerConfigs): Boolean {}
// ...
if (startServer(serverConfigs)) {
// ...
}
Copy
1Output: -
It can feel logical to return the server status after start-up but this is mixing two things. A function can be a command like "start the server" or it can be a query like "is the server healthy" not both at the same time. This is a common pattern called Command and Query Separation (CQS).
Following the single responsibility principle will eliminate questions like "How many lines of code is ideal for a function?" as limiting the function to do only thing will drammatically decrease its size.
A function is called pure when it has no side effects. A side effect can be logging to the console, sending a Kafka message, sending an email, modifying the input parameters, starting a new thread, opening/closing a port, creating/updating a record in the database etc. Side effects are state changes that is caused by the function. Majority of side effects are hard to test and debug.
Copy
public void addPassingStudents(List<Student> students, List<Student> passingStudents) {
for (Student student : students) {
if (student.getGpa() > 70) {
passingStudents.add(student);
}
}
}
Copy
1Output: -
Copy
fun addPassingStudents(students: List<Student>, passingStudents: MutableList<Student>) {
for (student in students) {
if (student.getGpa() > 70) {
passingStudents.add(student)
}
}
}
Copy
1Output: -
Copy
public List<Student> findPassingStudents(List<Student> students) {
return students.stream()
.filter(student -> student.getGpa() > 70)
.collect(Collectors.toList());
}
Copy
1Output: -
Copy
fun findPassingStudents(students: List<Student>): List<Student> =
students.asSequence()
.filter { it.getGpa() > 70 }
.toList()
Copy
1Output: -
Names that follow conventions and state the intention clearly, do not need comments. If you follow all mentioned principles then you don’t need comments for your variables and classes. Likewise, a well-defined small function that has a single responsibility doesn’t need extra explanation then its own name and body.
Using comments might seem like a good idea at first as it is easy to explain what’s going on in plain English. However, comments hide bad code. Even worse, they can become misleading after some time. Comments don’t compile so there’s no way for the IDE to warn you after modifying your method. You have to remember modifying the comments as well which often doesn’t happen.
Comments can be used during feature development, bug fixing but they shouldn't exist in production-grade code, they shouldn't even reach pull request reviews.
The only example I could find where comments are useful is legal comments as mentioned by Robert C. Martin in his book Clean Code. An example can be seen below.
Copy
/*
* Copyright (c) [year] [company name].
*
* This software is the confidential and proprietary information of [company name]. You shall
* not disclose or use it in whole or in part without the express written consent of [company name].
*
* Licensed under the [company name] License, Version [version number] (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.[company website].com/licenses/[license name]
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
Copy
1Output: -
In general, a guard clause is an early return or throw statement placed at the beginning of a function that checks for a specific condition that, if not met, makes it impossible to execute the remaining code safely or correctly. They help reduce the complexity of the code. Use guards clauses instead of nested if-else statements.
Copy
public void processOrder(Order order) {
if (order.getStatus().equals("pending")) {
if (order.getPaymentStatus().equals("paid")) {
if (order.getShippingAddress() != null) {
// process the order
}
}
}
}
Copy
1Output: -
Copy
fun processOrder(order: Order) {
if (order.getStatus().equals("pending")) {
if (order.getPaymentStatus().equals("paid")) {
if (order.getShippingAddress() != null) {
// process the order
}
}
}
}
Copy
1Output: -
Copy
public void processOrder(Order order) {
if (!order.getStatus().equals("pending")) {
return;
}
if (!order.getPaymentStatus().equals("paid")) {
return;
}
if (order.getShippingAddress() == null) {
return;
}
// Process the order
}
Copy
1Output: -
Copy
fun processOrder(order: Order) {
if (!order.getStatus().equals("pending")) {
return
}
if (!order.getPaymentStatus().equals("paid")) {
return
}
if (order.getShippingAddress() == null) {
return
}
// Process the order
}
Copy
1Output: -
Immutability simply means that once an object is created its state cannot be changed. An example immutable User class can be seen below.
Copy
public record User(
String id,
String email,
String fullName,
int age
) {}
Copy
1Output: -
Copy
public class User {
private final String id;
private final String email;
private final String fullName;
private final int age;
public User(String id, String email, String fullName, int age) {
this.id = id;
this.email = email;
this.fullName = fullName;
this.age = age;
}
public String getId() { return id; }
public String getEmail() { return email; }
public String getFullName() { return fullName; }
public int getAge() { return age; }
}
Copy
1Output: -
Copy
data class User(
val id: String,
val email: String,
val fullName: String,
val age: Int
)
Copy
1Output: -
One thing to be careful here is that all fields should be immutable as well. A mutable collection or an object would break the immutability.
Making immutability the default in the codebase has many advantages. Immutable objects are simple, predictable, thread-safe and can be cached because their properties do not change. Immutability works well with pure functions as well, it can even be said that they complete each other. Pure functions do not modify the input parameters but it is even better if the parameters can't be modified by nature.
To create a new updated immutable object from an existing immutable object, Lombok's toBuilder annotation can be used. For Kotlin, there is a built-in method called copy().
Copy
import lombok.Builder;
@Builder(toBuilder = true)
public class User {
// ...
}
Copy
1Output: -
Copy
// Use copy() for the same behaviour
Copy
1Output: -
Copy
public static void main(String[] args) {
final var user = new User("1345" ,"test@mail.com", "John Doe", 23);
final var updatedUser = user.toBuilder().age(33).build();
}
Copy
1Output: -
Copy
fun main() {
val user = User("1345" ,"test@mail.com", "John Doe", 23)
val updatedUser = user.copy(age = 33)
}
Copy
1Output: -
Immutable classes should be preferred over mutable ones.
A class, that cannot be instantiated and has only static methods which have no state is called a utility class or helper class.
Utility classes break so many principles. The first problem is tight coupling. The classes that depend on utility classes are tightly coupled. The class should depend on an interface or a class that is extendible.
Furthermore, testing becomes problematic. Although not impossible, it is difficult to test static methods. There are libraries that can mock static methods but why introduce new dependencies to the application?
Another issue is that use of AOP (Aspect Oriented Programming) with utility classes which is a frequently used pattern in Spring framework. Spring uses dynamic proxying to handle cross-cutting concerns which won't work with class full of static methods.
Final problem I will mention, and the one should scare developers the most, is the fact that a utility class has no boundries, it can grow forever. Say you have a CheckoutHelper. Any new method that has no state and is in the checkout domain can go into CheckoutHelper and it goes. After time, the class becomes huge and hard to maintain. This can even lead to helper classes that help other helper classes - a total mess.
Utility classes, as defined above, should be avoided. My favourite comment on utility classes belongs to this discussion on StackOverflow where a user named VGR says: "Notice that we call a toaster, toaster. We do not call it a BreadUtil."
Dependency injection is a design pattern in which an object's dependencies are passed in as external dependencies, rather than being created within the object itself. There are different types of injections such as constructor injection, setter injection and field injection.
The reason why I gave dependency injection its own section is because Spring framework makes it so easy to use all injection types. But great power comes with great responsibility! Using field injections with @Autowired annotation might be convenient but constructor injection has much better advantages than just ease of use.
Constructor injection allows dependencies to be marked as final resulting in compile-time safety unlike field or setter injection. It improves the testability of the class. Dependencies are explicitly defined as constructor parameters, meaning they are visible and easier to manage. It also allows different dependency injection frameworks to be used together. Therefore, constructor injection should be preferred over other injection types.
"Design patterns are reusable solutions to recurring software design problems that have been proven effective and efficient through repeated use." (Gang of Four, Design Patterns) As stated, design patterns are great tools. They should be used when the opportunity comes, however, when they are used just for the sake of using them, they create unnecessary maintenance overhead.
Copy
// bad examples
public interface CustomerService {}
public class CustomerServiceImpl implements CustomerService {}
public interface CustomerController {}
public class CustomerControllerImpl implements CustomerController {}
public interface CustomerDao {}
public class CustomerDaoImpl implements CustomerDao{}
Copy
1Output: -
Copy
// bad examples
interface CustomerService
class CustomerServiceImpl : CustomerService
interface CustomerController
class CustomerControllerImpl : CustomerController
interface CustomerDao
class CustomerDaoImpl : CustomerDao
Copy
1Output: -
There are two common packaging structure conventions: package by layer and package by feature.
Copy
├── com.app
└── controller
├── OrderController
└── UserController
└── domain
├── Order
└── User
└── repository
├── OrderRepository
└── UserRepository
└── service
├── OrderService
└── UserService
Copy
1Output: -
Copy
├── com.app
└── order
├── Order
├── OrderController
├── OrderRepository
└── OrderService
└── user
├── User
├── UserController
├── UserRepository
└── UserService
Copy
1Output: -
There is a strong relationship between test coverage and refactorability of a codebase. When a codebase has high test coverage developers can confidently make changes to the codebase without worrying about breaking existing functionality.
In contrast, low test coverage can make it difficult to refactor a codebase because there is a higher risk of introducing bugs or breaking existing functionality. Without automated tests in place, developers may be hesitant to make changes to the codebase out of fear that they will cause problems down the line. This can lead to a stagnant codebase that is difficult to maintain and update over time.
Open source libraries are usually developed, maintained, tested and documented by many developers which means the code is often of high quality. There is no need to re-invent the wheel. Development teams should always look for opportunities to use open source libraries. This will allow them to focus on the code quality of the unique features they build. Good examples are Apache Commons and Google Guava.
To sum up, adhering to clean code principles is crucial for building high-quality software. While implementing all of these principles may require some extra effort, the benefits of a well-designed, easy-to-read and maintainable codebase are well worth it in the long run. By consistently following these principles, developers can create software that not only works well but is also a pleasure to work with.