Category: logicDifficulty: MediumPublished: 2024-12-18
Understanding and Fixing Boolean Condition Errors
Boolean condition errors occur when logical expressions don't accurately represent the intended program logic. These bugs can be subtle and often lead to unexpected behavior that's hard to debug.
Common Boolean Logic Errors
1. Operator Precedence
// Problem: Misunderstanding operator precedence function isValidRange(value) { return 0 <= value <= 100; // Always returns true! } // Solution: Use proper boolean operators function isValidRangeFixed(value) { return value >= 0 && value <= 100; } // More examples of precedence issues console.log(true && false || true); // true console.log(true && (false || true)); // true console.log((true && false) || true); // true
2. Short-Circuit Evaluation
// Problem: Unintended short-circuit function processUser(user) { if (user && user.isAdmin || user.hasPermission) { // Throws if user is null grantAccess(); } } // Solution: Proper grouping and null checks function processUserFixed(user) { if (user && (user.isAdmin || user.hasPermission)) { grantAccess(); } } // More examples const value = null; console.log(value && value.property); // null (safe) console.log(value || value.property); // throws error console.log(value?.property || 'default'); // 'default' (safe)
3. De Morgan's Laws
// Problem: Complex negations function isInvalidInput(value) { return !(value && value.length > 0 && value[0] !== '-'); } // Solution: Apply De Morgan's Laws function isInvalidInputFixed(value) { return !value || value.length === 0 || value[0] === '-'; } // More examples const a = true, b = false; console.log(!(a && b) === (!a || !b)); // true console.log(!(a || b) === (!a && !b)); // true
Prevention Strategies
1. Boolean Expression Validator
class BooleanValidator { static validateComparison(value, options) { const { min = -Infinity, max = Infinity, inclusive = true } = options; if (inclusive) { return value >= min && value <= max; } return value > min && value < max; } static validateRange(value, ranges) { return ranges.some(([min, max]) => this.validateComparison(value, { min, max })); } static validateConditions(conditions) { return conditions.every(condition => { if (typeof condition === 'function') { return condition(); } return Boolean(condition); }); } } // Usage const value = 25; console.log(BooleanValidator.validateComparison(value, { min: 0, max: 100 })); // true
2. Condition Builder
class ConditionBuilder { constructor() { this.conditions = []; } and(condition) { this.conditions.push(['AND', condition]); return this; } or(condition) { this.conditions.push(['OR', condition]); return this; } not(condition) { this.conditions.push(['NOT', condition]); return this; } evaluate(context = {}) { return this.conditions.reduce((result, [operator, condition]) => { const value = typeof condition === 'function' ? condition(context) : condition; switch (operator) { case 'AND': return result && value; case 'OR': return result || value; case 'NOT': return !value; default: return result; } }, true); } } // Usage const isValidUser = new ConditionBuilder() .and(user => user.age >= 18) .and(user => user.isVerified) .or(user => user.isAdmin) .evaluate({ age: 20, isVerified: true });
3. Predicate Functions
class Predicates { static isNull(value) { return value === null || value === undefined; } static isNotNull(value) { return !this.isNull(value); } static isEmpty(value) { if (this.isNull(value)) return true; if (Array.isArray(value)) return value.length === 0; if (typeof value === 'object') return Object.keys(value).length === 0; if (typeof value === 'string') return value.trim().length === 0; return false; } static isNotEmpty(value) { return !this.isEmpty(value); } static isBetween(value, min, max) { return value >= min && value <= max; } } // Usage function processInput(value) { if (Predicates.isEmpty(value)) { throw new Error('Input cannot be empty'); } if (typeof value === 'number' && !Predicates.isBetween(value, 0, 100)) { throw new Error('Value must be between 0 and 100'); } return value; }
Testing Strategies
1. Truth Table Tests
describe('Boolean Logic Tests', () => { it('should handle complex conditions correctly', () => { const truthTable = [ { a: true, b: true, expected: true }, { a: true, b: false, expected: false }, { a: false, b: true, expected: false }, { a: false, b: false, expected: false } ]; function complexCondition(a, b) { return a && b; } truthTable.forEach(({ a, b, expected }) => { expect(complexCondition(a, b)).toBe(expected); }); }); });
2. Boundary Tests
describe('Boundary Tests', () => { it('should validate ranges correctly', () => { const validator = new BooleanValidator(); // Test boundaries expect(validator.validateComparison(0, { min: 0, max: 100 })).toBe(true); expect(validator.validateComparison(100, { min: 0, max: 100 })).toBe(true); expect(validator.validateComparison(-1, { min: 0, max: 100 })).toBe(false); expect(validator.validateComparison(101, { min: 0, max: 100 })).toBe(false); // Test exclusive boundaries expect(validator.validateComparison(50, { min: 0, max: 100, inclusive: false })).toBe(true); }); });
3. Edge Case Tests
describe('Edge Case Tests', () => { it('should handle null and undefined', () => { expect(Predicates.isNull(null)).toBe(true); expect(Predicates.isNull(undefined)).toBe(true); expect(Predicates.isNull(false)).toBe(false); expect(Predicates.isNull('')).toBe(false); expect(Predicates.isNull(0)).toBe(false); }); it('should handle empty values', () => { expect(Predicates.isEmpty([])).toBe(true); expect(Predicates.isEmpty({})).toBe(true); expect(Predicates.isEmpty('')).toBe(true); expect(Predicates.isEmpty(' ')).toBe(true); expect(Predicates.isEmpty([1])).toBe(false); }); });
Best Practices
-
Use Explicit Comparisons
// Bad if (value) { // Implicit conversion to boolean } // Good if (value !== null && value !== undefined) { // Explicit comparison }
-
Group Complex Conditions
// Bad if (a && b || c && d) { // Ambiguous precedence } // Good if ((a && b) || (c && d)) { // Clear precedence }
-
Use Early Returns
// Bad function processValue(value) { let result; if (value > 0) { if (value < 100) { result = value; } } return result; } // Good function processValue(value) { if (value <= 0) return null; if (value >= 100) return null; return value; }
Common Gotchas
-
Truthy/Falsy Values
// Unexpected truthy values console.log(Boolean([])); // true console.log(Boolean({})); // true console.log(Boolean(' ')); // true // Unexpected falsy values console.log(Boolean(0)); // false console.log(Boolean('')); // false console.log(Boolean(NaN)); // false
-
Equality vs Identity
// Loose equality console.log('' == false); // true console.log(0 == false); // true console.log(null == undefined); // true // Strict equality console.log('' === false); // false console.log(0 === false); // false console.log(null === undefined); // false
-
Operator Precedence
// Unexpected precedence console.log(1 + 2 * 3); // 7, not 9 console.log(true || false && false); // true console.log(true && false || true); // true // Fixed with grouping console.log((1 + 2) * 3); // 9 console.log(true || (false && false)); // true console.log((true && false) || true); // true