| name | fail-fast |
| description | Guard clauses instead of nested if/else in Kotlin — require/requireNotNull/check matched to the original exception type, flattening nested pyramids into sequential early returns, "?: return" chains for nullables, resource cleanup on every early-return path, and when not to invert a branch. Use for any code with preconditions, nullable values, or nested conditionals. |
Fail Fast: Guard Clauses Over Nested if/else
Write new conditionals this way, and invert Java's nested-if pyramids into it when you
touch them: guard clauses that return (or throw) early, leaving the happy path at the lowest
indentation. Behaviour is identical — the branches are the same, only the shape changes.
Examples come from the wider nextcloud/android codebase; the class names are not from this
repository.
Precondition Checks → require / requireNotNull
requireNotNull returns the smart-cast non-null value AND throws
IllegalArgumentException with the message — exactly matching the Java if (x == null) throw new IllegalArgumentException(...).
if (file == null) throw IllegalArgumentException("File may not be null");
if (user == null) throw IllegalArgumentException("Account may not be null");
fileActivity = (FileActivity) getActivity();
if (fileActivity == null) throw IllegalArgumentException("FileActivity may not be null");
fileActivity = activity as? FileActivity
requireNotNull(file) { "File may not be null" }
requireNotNull(user) { "Account may not be null" }
requireNotNull(fileActivity) { "FileActivity may not be null" }
Use require(condition) { msg } for boolean preconditions:
require(activity is FileActivity) { "Calling activity must be of type FileActivity" }
check/checkNotNull are the IllegalStateException equivalents — use them when the Java
threw IllegalStateException. Match the original exception type; that is observable
behaviour.
Early Return Over Nested Success Path
private void checkShareViaUser() {
if (!MDMConfig.INSTANCE.shareViaUser(requireContext())) {
binding.searchContainer.setVisibility(View.GONE);
}
}
private fun checkShareViaUser() {
if (shareViaUser(requireContext())) return
binding?.searchContainer?.visibility = View.GONE
}
Deeply Nested if/else → Sequential Guards
The most valuable transformation. A cursor-handling method nested three levels deep
becomes a flat sequence of guard clauses, each handling one failure and returning.
private fun handleContactResult(contactUri: Uri) {
val cursor = fileActivity?.contentResolver?.query(contactUri, projection, null, null, null)
if (cursor == null) {
DisplayUtils.showSnackMessage(this, R.string.email_pick_failed)
Log_OC.e(TAG, "Failed to pick email address as Cursor is null.")
return
}
if (!cursor.moveToFirst()) {
DisplayUtils.showSnackMessage(this, R.string.email_pick_failed)
Log_OC.e(TAG, "Failed to pick email address as no Email found.")
return
}
val columnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS)
if (columnIndex == -1) {
DisplayUtils.showSnackMessage(this, R.string.email_pick_failed)
Log_OC.e(TAG, "Failed to pick email address.")
cursor.close()
return
}
val email = cursor.getString(columnIndex)
cursor.close()
}
Watch the cleanup: if the Java relied on falling through to a single cursor.close(),
each early return must still close it (or wrap in use {}). Missing that changes
behaviour (resource leak) — verify it.
Nullable-Guard Idioms
val activity = fileActivity ?: return
val clientRepository = activity.clientRepository ?: return
val remotePath = file?.remotePath ?: return
Each ?: return collapses one Java if (x == null) return;. Chain them at the top of the
function so the body works with non-null smart-cast locals.
When NOT to Invert
- Do not turn a genuine two-branch decision (both branches do real work) into a guard if
it obscures the symmetry — a
when/if-else expression is clearer there.
- Do not change the order of side-effects while inverting; the snackbar/log calls above
must fire in the same cases as before.