Skip to content

Commit 72bfda5

Browse files
committed
1.2.19
1 parent f8467e1 commit 72bfda5

File tree

3 files changed

+445
-32
lines changed

3 files changed

+445
-32
lines changed

docs/app_best_practices.md

Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
Here's a best practices document for developing apps in this project:
2+
3+
# Best Practices for SkyeNet Apps
4+
5+
## Application Architecture
6+
7+
### Core Design Principles
8+
9+
1. Single Responsibility:
10+
11+
```kotlin
12+
// Each class should have a single, well-defined purpose
13+
class DocumentProcessor(
14+
val parser: DocumentParser,
15+
val validator: DocumentValidator,
16+
val storage: DocumentStorage
17+
)
18+
```
19+
20+
2. Dependency Injection:
21+
22+
```kotlin
23+
class MyApp(
24+
private val api: API,
25+
private val storage: StorageService,
26+
private val validator: ValidationService
27+
) : ApplicationServer {
28+
// Dependencies are injected rather than created internally
29+
}
30+
```
31+
32+
### Application Structure
33+
34+
1. Separate core logic from UI:
35+
36+
```kotlin
37+
class MyApp(
38+
// Core configuration
39+
val settings: Settings,
40+
val model: ChatModel,
41+
// UI configuration
42+
applicationName: String = "My App",
43+
path: String = "/myapp"
44+
) : ApplicationServer(applicationName, path) {
45+
// Core business logic methods
46+
private fun processData() {}
47+
48+
// UI handling methods
49+
override fun userMessage() {}
50+
}
51+
```
52+
53+
2. Use immutable data classes for configuration:
54+
55+
```kotlin
56+
data class Settings(
57+
val maxItems: Int = 100,
58+
val timeout: Duration = Duration.ofMinutes(5),
59+
val features: Set<Feature> = setOf(),
60+
val retryConfig: RetryConfig = RetryConfig(),
61+
val validationRules: List<ValidationRule> = listOf()
62+
)
63+
data class RetryConfig(
64+
val maxAttempts: Int = 3,
65+
val backoffMs: Long = 1000
66+
)
67+
```
68+
69+
3. Handle state management:
70+
71+
```kotlin
72+
// Per-session state
73+
private val sessionState = mutableMapOf<String, SessionState>()
74+
// Thread-safe state updates
75+
private val stateGuard = AtomicBoolean(false)
76+
77+
// Immutable state updates
78+
fun updateState(sessionId: String, update: (SessionState) -> SessionState) {
79+
synchronized(stateGuard) {
80+
sessionState[sessionId] = update(sessionState[sessionId] ?: SessionState())
81+
}
82+
}
83+
```
84+
85+
## Logging Best Practices
86+
87+
### Logging Guidelines
88+
89+
1. Use consistent log formats:
90+
91+
```kotlin
92+
private fun logEvent(
93+
event: String,
94+
data: Map<String, Any?>,
95+
level: LogLevel = LogLevel.INFO
96+
) {
97+
when (level) {
98+
LogLevel.DEBUG -> log.debug("$event: ${data.toJson()}")
99+
LogLevel.INFO -> log.info("$event: ${data.toJson()}")
100+
LogLevel.WARN -> log.warn("$event: ${data.toJson()}")
101+
LogLevel.ERROR -> log.error("$event: ${data.toJson()}")
102+
}
103+
}
104+
```
105+
106+
### Sub-Log Creation
107+
108+
1. Create child API clients with dedicated logs for each major operation:
109+
110+
```kotlin
111+
val api = (api as ChatClient).getChildClient().apply {
112+
val createFile = task.createFile(".logs/api-${UUID.randomUUID()}.log")
113+
createFile.second?.apply {
114+
logStreams += this.outputStream().buffered()
115+
task.verbose("API log: <a href=\"file:///$this\">$this</a>")
116+
}
117+
}
118+
```
119+
120+
### Structured Logging
121+
122+
```kotlin
123+
log.info(
124+
"Processing request", mapOf(
125+
"userId" to user.id,
126+
"requestType" to requestType,
127+
"timestamp" to System.currentTimeMillis(),
128+
"context" to mapOf(
129+
"session" to session.id,
130+
"environment" to env,
131+
"features" to enabledFeatures
132+
)
133+
)
134+
)
135+
```
136+
137+
2. Use appropriate log levels:
138+
139+
```kotlin
140+
log.debug("Fine-grained diagnostic info")
141+
log.info("General operational events")
142+
log.warn("Potentially harmful situations")
143+
log.error("Error events that might still allow the app to continue")
144+
// Add context to error logs
145+
log.error(
146+
"Operation failed", mapOf(
147+
"error" to e.message,
148+
"stackTrace" to e.stackTraceToString(),
149+
"context" to operationContext
150+
)
151+
)
152+
```
153+
154+
## Resource Management
155+
156+
### API Client Lifecycle
157+
158+
```kotlin
159+
// Use structured resource management
160+
inline fun <T> withAPI(crossinline block: (API) -> T): T {
161+
return api.use { client ->
162+
try {
163+
block(client)
164+
} finally {
165+
client.close()
166+
}
167+
}
168+
}
169+
170+
api.use { client ->
171+
try {
172+
// Use API client
173+
} finally {
174+
client.close()
175+
}
176+
}
177+
```
178+
179+
### Memory Management
180+
181+
```kotlin
182+
// Use sequences for large collections and implement pagination
183+
files.asSequence()
184+
.filter { it.length() < maxSize }
185+
.map { process(it) }
186+
.take(limit)
187+
.chunked(pageSize)
188+
.toList()
189+
// Implement resource pooling
190+
val resourcePool = ResourcePool<ExpensiveResource>(
191+
maxSize = 10,
192+
factory = { createExpensiveResource() }
193+
)
194+
195+
// Clear buffers after use
196+
buffer.clear()
197+
```
198+
199+
3. Include contextual information in logs:
200+
201+
```kotlin
202+
log.info("Processing user message: $userMessage")
203+
log.error("Error processing task ${taskId}", exception)
204+
```
205+
206+
## Exception Handling
207+
208+
### General Exception Handling Pattern
209+
210+
```kotlin
211+
try {
212+
// Main operation
213+
checkPreconditions()
214+
validateInput(data)
215+
} catch (e: SocketTimeoutException) {
216+
log.error("Network timeout", e)
217+
task.add("The operation timed out. Please check your network connection.")
218+
} catch (e: IOException) {
219+
log.error("I/O error", e)
220+
task.add("An I/O error occurred. Please try again later.")
221+
} catch (e: IllegalStateException) {
222+
log.error("Invalid state", e)
223+
task.add("Operation cannot be completed in current state")
224+
} catch (e: Exception) {
225+
log.error("Unexpected error", e)
226+
task.error(ui, e)
227+
} finally {
228+
cleanup()
229+
task.complete()
230+
}
231+
```
232+
233+
### Input Validation
234+
235+
```kotlin
236+
fun validate(input: UserInput) {
237+
require(input.name.isNotBlank()) { "Name cannot be empty" }
238+
require(input.age in 0..150) { "Invalid age" }
239+
check(isInitialized) { "System not initialized" }
240+
}
241+
```
242+
243+
### User-Friendly Error Messages
244+
245+
```kotlin
246+
when (e) {
247+
is IllegalArgumentException -> task.add("Invalid input: ${e.message}")
248+
is IllegalStateException -> task.add("Operation failed: ${e.message}")
249+
is SecurityException -> task.add("Access denied: ${e.message}")
250+
else -> task.add("An unexpected error occurred. Please try again later.")
251+
}
252+
```
253+
254+
## Using Retry and Tabs
255+
256+
### Retryable Operations
257+
258+
1. Wrap retryable operations using the Retryable class:
259+
260+
```kotlin
261+
Retryable(ui, task) { content ->
262+
try {
263+
// Operation that might need retry
264+
val result = performOperation()
265+
renderMarkdown(result, ui = ui)
266+
} catch (e: Exception) {
267+
task.error(ui, e)
268+
"Error: ${e.message}"
269+
}
270+
}
271+
```
272+
273+
### Tab Management
274+
275+
1. Create organized tab displays:
276+
277+
```kotlin
278+
val tabbedDisplay = TabbedDisplay(task)
279+
tabbedDisplay["Tab Name"] = content
280+
tabbedDisplay.update()
281+
```
282+
283+
2. Handle nested tabs:
284+
285+
```kotlin
286+
val parentTabs = TabbedDisplay(task)
287+
val childTask = ui.newTask(false)
288+
parentTabs["Parent Tab"] = childTask.placeholder
289+
val childTabs = TabbedDisplay(childTask)
290+
```
291+
292+
## Task Status Management
293+
294+
### Task Lifecycle States
295+
296+
1. Initial State:
297+
298+
```kotlin
299+
val task = ui.newTask()
300+
task.add(SessionTask.spinner) // Show loading spinner
301+
```
302+
303+
2. In Progress:
304+
305+
```kotlin
306+
task.add("Processing...") // Update status
307+
task.verbose("Detailed progress info") // Show detailed progress
308+
```
309+
310+
3. Completion:
311+
312+
```kotlin
313+
task.complete() // Normal completion
314+
task.complete("Operation completed successfully") // Completion with message
315+
```
316+
317+
4. Error State:
318+
319+
```kotlin
320+
task.error(ui, exception) // Show error with details
321+
```
322+
323+
### Progress Tracking
324+
325+
1. Use progress bars for long operations:
326+
327+
```kotlin
328+
val progressBar = progressBar(ui.newTask())
329+
progressBar.add(completedItems.toDouble(), totalItems.toDouble())
330+
```
331+
332+
## Best Practices for Task Organization
333+
334+
1. Break down complex operations into subtasks:
335+
336+
```kotlin
337+
val mainTask = ui.newTask()
338+
val subTask1 = ui.newTask(false)
339+
val subTask2 = ui.newTask(false)
340+
mainTask.verbose(subTask1.placeholder)
341+
mainTask.verbose(subTask2.placeholder)
342+
```
343+
344+
2. Use meaningful task headers:
345+
346+
```kotlin
347+
task.header("Processing Stage 1")
348+
// ... operations ...
349+
task.header("Processing Stage 2")
350+
```
351+
352+
3. Provide visual feedback:
353+
354+
```kotlin
355+
task.add(
356+
MarkdownUtil.renderMarkdown("""
357+
## Current Progress
358+
- Step 1: Complete ✓
359+
- Step 2: In Progress ⟳
360+
- Step 3: Pending ○
361+
""", ui = ui
362+
)
363+
)
364+
```
365+
366+
## Memory and Resource Management
367+
368+
1. Clean up resources:
369+
370+
```kotlin
371+
try {
372+
// Use resources
373+
} finally {
374+
resource.close()
375+
task.complete()
376+
}
377+
```
378+
379+
2. Handle large data efficiently:
380+
381+
```kotlin
382+
fun truncate(output: String, kb: Int = 32): String {
383+
if (output.length > 1024 * 2 * kb) {
384+
return output.substring(0, 1024 * kb) +
385+
"\n\n... Output truncated ...\n\n" +
386+
output.substring(output.length - 1024 * kb)
387+
}
388+
return output
389+
}
390+
```
391+
392+
Following these best practices will help ensure your apps are reliable, maintainable, and provide a good user experience.

0 commit comments

Comments
 (0)