This document outlines a comprehensive refactoring plan to address code quality, maintainability, and reliability issues identified in the DazScriptServer codebase. The plan is organized into phases to minimize risk and maintain functionality throughout the refactoring process.
- Backward Compatibility: Maintain API compatibility throughout refactoring
- Incremental Changes: Small, testable changes that can be merged frequently
- Test Coverage First: Add tests before refactoring where possible
- Documentation: Update documentation alongside code changes
- No Feature Creep: Focus purely on refactoring, not new features
Goal: Establish testing infrastructure and fix critical thread safety issues.
- Set up testing framework (Google Test or Qt Test)
- Add CMake test targets
- Create test utilities for mocking DAZ SDK components
- Establish CI pipeline for automated testing
- Priority: CRITICAL
- Replace
m_nActiveRequestswithQAtomicIntor add mutex protection - Audit all shared state accessed from HTTP threads
- Add thread sanitizer to CI builds
- Document thread safety guarantees for each class
- Replace raw
new DzScript()withQScopedPointer<DzScript> - Add RAII wrappers for signal/slot connections
- Fix potential leaks in error paths
Deliverables:
- Working test framework with 5+ example tests
- Thread-safe request counter
- Zero memory leaks reported by Valgrind/ASAN
- CI pipeline running on every commit
Goal: Break down the god object into focused, testable components.
Extract authentication logic into AuthenticationService:
class AuthenticationService {
public:
explicit AuthenticationService(const QString& tokenFilePath);
bool validateToken(const QString& token) const;
QString generateNewToken();
bool loadToken();
void saveToken();
QString currentToken() const;
signals:
void tokenChanged(const QString& newToken);
void authenticationFailed(const QString& clientIP);
};Tests: Token validation, generation, persistence, concurrency
Extract rate limiting into RateLimiterService:
class RateLimiterService {
public:
RateLimiterService(int maxRequests, int windowSeconds);
bool checkRateLimit(const QString& clientIP);
void reset(const QString& clientIP);
QMap<QString, int> getCurrentLimits() const;
private:
void cleanup(); // Remove old timestamps
};Tests: Rate limit enforcement, sliding window, cleanup, edge cases
Extract IP filtering into IPWhitelistService:
class IPWhitelistService {
public:
explicit IPWhitelistService(const QStringList& allowedIPs);
bool isAllowed(const QString& clientIP) const;
void setWhitelist(const QStringList& ips);
QStringList whitelist() const;
};Tests: IP matching, CIDR support (future), wildcard support
Extract metrics into MetricsCollector:
class MetricsCollector {
public:
void recordRequest(bool success, qint64 durationMs);
void recordAuthFailure();
MetricsSnapshot snapshot() const;
QJsonObject toJson() const;
private:
mutable QMutex m_mutex;
RequestMetrics m_metrics;
};Tests: Concurrent recording, statistics accuracy, persistence
Deliverables:
- 4 new service classes with 80%+ test coverage
DzScriptServerPanereduced by 500+ lines- All services independently testable
Goal: Separate HTTP routing from business logic.
Define clean handler interface:
class IRequestHandler {
public:
virtual ~IRequestHandler() = default;
virtual QByteArray handle(const HttpRequest& req) = 0;
};
struct HttpRequest {
QByteArray body;
QString clientIP;
QMap<QString, QString> headers;
QMap<QString, QString> queryParams;
};Create focused handler classes:
ExecuteScriptHandler- Synchronous script executionAsyncExecuteHandler- Async script executionScriptRegistryHandler- Script registry CRUDStatusHandler- Health and metrics endpointsAsyncStatusHandler- Async request status queries
Each handler encapsulates:
- Input validation
- Business logic
- Error handling
- Response formatting
Implement middleware pattern for cross-cutting concerns:
class Middleware {
public:
virtual QByteArray process(const HttpRequest& req,
std::function<QByteArray()> next) = 0;
};
class AuthenticationMiddleware : public Middleware { /* ... */ };
class RateLimitMiddleware : public Middleware { /* ... */ };
class IPWhitelistMiddleware : public Middleware { /* ... */ };
class LoggingMiddleware : public Middleware { /* ... */ };
class BodySizeMiddleware : public Middleware { /* ... */ };Deliverables:
- Handler interface with 5+ implementations
- Middleware chain with 80%+ test coverage
- Route handlers reduced to simple delegation
- Zero code duplication in authentication/validation
Goal: Isolate and harden async request queue management.
Extract into AsyncRequestManager:
class AsyncRequestManager : public QObject {
public:
QString submitRequest(const QString& scriptText, const QVariantMap& args);
AsyncRequestStatus status(const QString& requestId) const;
AsyncRequestResult result(const QString& requestId) const;
bool cancel(const QString& requestId);
QList<AsyncRequestInfo> listRequests(const QString& statusFilter) const;
signals:
void requestQueued(const QString& requestId);
void requestStarted(const QString& requestId);
void requestCompleted(const QString& requestId);
void requestFailed(const QString& requestId, const QString& error);
void requestCancelled(const QString& requestId);
private slots:
void processNextRequest();
void cleanupExpiredRequests();
};Add memory and resource limits:
- Maximum queue depth (reject new requests if full)
- Maximum total requests tracked
- Aggressive TTL-based cleanup
- Memory usage monitoring
Improve cancellation safety:
- Use atomic flags for cancellation
- Add timeout to killRender() operations
- Better synchronization with main thread
- Cancellation acknowledgment
Deliverables:
AsyncRequestManagerwith comprehensive tests- Memory bounds enforced
- Graceful degradation under load
- Documented threading model
Goal: Consistent, robust error handling throughout.
Create structured error responses:
enum class ErrorCode {
INVALID_REQUEST,
AUTHENTICATION_REQUIRED,
AUTHENTICATION_FAILED,
RATE_LIMIT_EXCEEDED,
IP_BLOCKED,
PAYLOAD_TOO_LARGE,
SCRIPT_TOO_LONG,
SERVER_BUSY,
SCRIPT_EXECUTION_FAILED,
INTERNAL_ERROR
};
class ErrorResponse {
public:
static QByteArray create(ErrorCode code,
const QString& message,
int httpStatus);
static QByteArray createWithDetails(ErrorCode code,
const QString& message,
const QVariantMap& details,
int httpStatus);
};Centralize validation:
class RequestValidator {
public:
ValidationResult validateExecuteRequest(const QVariantMap& body);
ValidationResult validateScriptRegistry(const QVariantMap& body);
ValidationResult validateScriptName(const QString& name);
private:
int m_maxScriptLength;
int m_maxBodySize;
};
struct ValidationResult {
bool valid;
ErrorCode errorCode;
QString errorMessage;
};Add startup validation:
- Verify crypto APIs available before starting
- Validate DAZ SDK integration
- Check file system permissions
- Refuse to start if critical components unavailable
Deliverables:
- Consistent error responses across all endpoints
- Centralized validation with tests
- Fail-fast behavior for critical errors
- Error code documentation
Goal: Eliminate duplication, magic numbers, and improve documentation.
- Remove duplicate
variantToJson()implementations - Extract common route handler patterns
- Consolidate JSON response building
- Share authentication logic
Extract all magic numbers to configuration:
namespace RequestConfig {
constexpr int ASYNC_STATUS_POLL_INTERVAL_MS = 500;
constexpr int REQUEST_ID_LENGTH = 8;
constexpr int METRICS_SAVE_INTERVAL = 10;
constexpr int ASYNC_CLEANUP_INTERVAL_MIN = 5;
constexpr int ASYNC_REQUEST_TTL_HOURS = 1;
}- API documentation for all public classes
- Threading model documentation per class
- Error handling guide for API consumers
- Architecture decision records (ADRs)
- Updated README with architecture diagrams
- Run clang-format across codebase
- Fix naming inconsistencies
- Standardize include order
- Remove commented-out code
Deliverables:
- Zero code duplication detected by static analysis
- All magic numbers converted to named constants
- 100% public API documentation coverage
- Architecture documentation updated
Goal: Robust, validated settings management.
Centralize settings management:
class SettingsService {
public:
static SettingsService& instance();
ServerSettings loadSettings();
void saveSettings(const ServerSettings& settings);
void migrateSettings(int fromVersion, int toVersion);
ValidationResult validate(const ServerSettings& settings);
private:
ServerSettings m_currentSettings;
QMutex m_mutex;
};
struct ServerSettings {
QString host;
int port;
bool authEnabled;
bool ipWhitelistEnabled;
QStringList allowedIPs;
bool rateLimitEnabled;
int rateLimitMax;
int rateLimitWindowSec;
int maxConcurrentRequests;
int maxBodySizeMB;
int maxScriptLengthKB;
int version; // Settings schema version
};- Version settings schema
- Provide migration paths for upgrades
- Validate loaded settings
- Fallback to defaults on corruption
Deliverables:
- Centralized settings service
- Settings validation
- Migration framework
- Settings persistence tests
Goal: Robust platform-specific code and security improvements.
- Retry with alternative sources on failure
- Log detailed crypto errors
- Refuse to start server if crypto unavailable
- Add periodic crypto health checks
- Add timeouts to all blocking operations
- Implement graceful shutdown with deadlines
- Clean up dangling resources on error paths
- Add resource usage monitoring
- Standardize on UTF-8 throughout
- Validate input encoding
- Document encoding assumptions
- Add encoding tests
Deliverables:
- Robust crypto initialization
- No indefinite blocking operations
- UTF-8 consistency
- Platform-specific code tested on Windows/macOS/Linux
Goal: Comprehensive end-to-end testing and performance validation.
- Full HTTP request/response tests
- Multi-threaded stress tests
- Authentication flow tests
- Async execution tests
- Error scenario tests
- Measure request throughput
- Test under concurrent load
- Memory usage profiling
- Long-running render tests
- Resource leak detection
- Simulate realistic workloads
- Test rate limiting under load
- Verify graceful degradation
- Measure recovery time
Deliverables:
- Integration test suite with 50+ tests
- Performance benchmarks baseline
- Load testing results documented
- No regressions vs. baseline
Goal: Complete documentation and prepare for release.
- OpenAPI/Swagger specification
- Client examples in multiple languages
- Error handling guide
- Best practices documentation
- System architecture diagrams
- Threading model diagrams
- Component interaction diagrams
- Deployment guide
- Guide for users upgrading from v1.x
- Breaking changes documentation
- Configuration migration steps
- Troubleshooting guide
- Changelog generation
- Version bump
- Release notes
- Backward compatibility verification
Deliverables:
- Complete API documentation
- Architecture documentation
- Migration guide
- v2.0 release candidate
- Test Coverage: >80% line coverage, >90% branch coverage for new code
- Code Duplication: <3% (measured by CPD or similar)
- Cyclomatic Complexity: No functions >15, average <5
- Lines per Class: Main pane class <500 lines (from 2376)
- Thread Safety: Zero data races detected by thread sanitizer
- Memory Safety: Zero leaks detected by Valgrind/ASAN
- Resource Cleanup: All resources cleaned up within 5s of shutdown
- Error Handling: 100% of error paths tested
- No Regressions: Performance within 5% of baseline
- Concurrent Requests: Handle 50+ concurrent requests without degradation
- Memory Growth: <10MB growth over 1000 requests
- Startup Time: <1 second from launch to ready
- Build Time: <5 minutes clean build
- Test Time: <30 seconds for full test suite
- Documentation: 100% public API documented
- New Contributor Onboarding: <2 hours to first contribution
- Threading Model Changes: Extensive testing required
- HTTP Library Updates: May break existing clients
- Settings Migration: Data loss potential
Mitigation: Feature flags, extensive testing, backup/restore functionality
- Maintain v1.x branch for critical fixes
- Feature flags for new architecture
- Automated rollback in CI/CD
- Database migration reversibility
- Pin dependency versions
- Regular security updates
- Dependency vulnerability scanning
- License compliance checks
| Phase | Duration | Dependencies | Risk |
|---|---|---|---|
| 1. Foundation & Safety | 2 weeks | None | Medium |
| 2. Extract Core Services | 3 weeks | Phase 1 | Low |
| 3. Request Handling | 3 weeks | Phase 2 | Medium |
| 4. Async Subsystem | 2 weeks | Phase 3 | High |
| 5. Error Handling | 2 weeks | Phase 3 | Low |
| 6. Code Quality | 2 weeks | All above | Low |
| 7. Settings | 1 week | Phase 2 | Low |
| 8. Platform Hardening | 1 week | Phase 7 | Medium |
| 9. Integration Testing | 1 week | All above | Low |
| 10. Documentation | 1 week | All above | Low |
Total Duration: ~18 weeks (4.5 months)
- Should we support breaking changes in v2.0 or maintain full backward compatibility?
- What's the minimum supported DAZ Studio version after refactoring?
- Should we add a plugin upgrade mechanism?
- Do we need to support downgrading from v2.0 to v1.x?
- Should async requests persist across server restarts?
This refactoring plan addresses all identified code quality issues while maintaining functionality and minimizing risk. The phased approach allows for incremental progress with regular validation checkpoints. Each phase delivers tangible improvements that can be released independently if needed.
Estimated Effort: 4-5 months with 1 full-time developer, or 2-3 months with 2 developers working in parallel on independent phases.