Development Best Practices
This guide outlines the best practices for developing tools and features for the Home Assistant MCP.
Code Style
TypeScript
- Use TypeScript for all new code
- Enable strict mode
- Use explicit types
- Avoid
any
type - Use interfaces over types
- Document with JSDoc comments
/**
* Represents a device in the system.
* @interface
*/
interface Device {
/** Unique device identifier */
id: string;
/** Human-readable device name */
name: string;
/** Device state */
state: DeviceState;
}
Naming Conventions
- Use PascalCase for:
- Classes
- Interfaces
- Types
-
Enums
-
Use camelCase for:
- Variables
- Functions
- Methods
-
Properties
-
Use UPPER_SNAKE_CASE for:
- Constants
- Enum values
class DeviceManager {
private readonly DEFAULT_TIMEOUT = 5000;
async getDeviceState(deviceId: string): Promise<DeviceState> {
// Implementation
}
}
Architecture
SOLID Principles
- Single Responsibility
- Each class/module has one job
-
Split complex functionality
-
Open/Closed
- Open for extension
-
Closed for modification
-
Liskov Substitution
- Subtypes must be substitutable
-
Use interfaces properly
-
Interface Segregation
- Keep interfaces focused
-
Split large interfaces
-
Dependency Inversion
- Depend on abstractions
- Use dependency injection
Example
// Bad
class DeviceManager {
async getState() { /* ... */ }
async setState() { /* ... */ }
async sendNotification() { /* ... */ } // Wrong responsibility
}
// Good
class DeviceManager {
constructor(
private notifier: NotificationService
) {}
async getState() { /* ... */ }
async setState() { /* ... */ }
}
class NotificationService {
async send() { /* ... */ }
}
Error Handling
Best Practices
- Use custom error classes
- Include error codes
- Provide meaningful messages
- Include error context
- Handle async errors
- Log appropriately
class DeviceError extends Error {
constructor(
message: string,
public code: string,
public context: Record<string, any>
) {
super(message);
this.name = 'DeviceError';
}
}
try {
await device.connect();
} catch (error) {
throw new DeviceError(
'Failed to connect to device',
'DEVICE_CONNECTION_ERROR',
{ deviceId: device.id, attempt: 1 }
);
}
Testing
Guidelines
- Write unit tests first
- Use meaningful descriptions
- Test edge cases
- Mock external dependencies
- Keep tests focused
- Use test fixtures
describe('DeviceManager', () => {
let manager: DeviceManager;
let mockDevice: jest.Mocked<Device>;
beforeEach(() => {
mockDevice = {
id: 'test_device',
getState: jest.fn()
};
manager = new DeviceManager(mockDevice);
});
it('should get device state', async () => {
mockDevice.getState.mockResolvedValue('on');
const state = await manager.getDeviceState();
expect(state).toBe('on');
});
});
Performance
Optimization
- Use caching
- Implement pagination
- Optimize database queries
- Use connection pooling
- Implement rate limiting
- Batch operations
class DeviceCache {
private cache = new Map<string, CacheEntry>();
private readonly TTL = 60000; // 1 minute
async getDevice(id: string): Promise<Device> {
const cached = this.cache.get(id);
if (cached && Date.now() - cached.timestamp < this.TTL) {
return cached.device;
}
const device = await this.fetchDevice(id);
this.cache.set(id, {
device,
timestamp: Date.now()
});
return device;
}
}
Security
Guidelines
- Validate all input
- Use parameterized queries
- Implement rate limiting
- Use proper authentication
- Follow OWASP guidelines
- Sanitize output
class InputValidator {
static validateDeviceId(id: string): boolean {
return /^[a-zA-Z0-9_-]{1,64}$/.test(id);
}
static sanitizeOutput(data: any): any {
// Implement output sanitization
return data;
}
}
Documentation
Standards
- Use JSDoc comments
- Document interfaces
- Include examples
- Document errors
- Keep docs updated
- Use markdown
/**
* Manages device operations.
* @class
*/
class DeviceManager {
/**
* Gets the current state of a device.
* @param {string} deviceId - The device identifier.
* @returns {Promise<DeviceState>} The current device state.
* @throws {DeviceError} If device is not found or unavailable.
* @example
* const state = await deviceManager.getDeviceState('living_room_light');
*/
async getDeviceState(deviceId: string): Promise<DeviceState> {
// Implementation
}
}
Logging
Best Practices
- Use appropriate levels
- Include context
- Structure log data
- Handle sensitive data
- Implement rotation
- Use correlation IDs
class Logger {
info(message: string, context: Record<string, any>) {
console.log(JSON.stringify({
level: 'info',
message,
context,
timestamp: new Date().toISOString(),
correlationId: context.correlationId
}));
}
}
Version Control
Guidelines
- Use meaningful commits
- Follow branching strategy
- Write good PR descriptions
- Review code thoroughly
- Keep changes focused
- Use conventional commits
# Good commit messages
git commit -m "feat(device): add support for zigbee devices"
git commit -m "fix(api): handle timeout errors properly"