Code guidelines | vasak-desktop
Standards and best practices for keeping the quality of the code up.
General principles#
Clarity first#
The code should be easy to understand:
1
2
3
4
5
6
7
8
9
10
| // ❌ Hard to read
fn calc(a: Vec<i32>) -> i32 { a.iter().fold(0, |acc, x| acc + if x % 2 == 0 { x } else { 0 }) }
// ✅ Clear
fn sum_even_numbers(numbers: Vec<i32>) -> i32 {
numbers
.iter()
.filter(|n| n % 2 == 0)
.sum()
}
|
Consistency#
Stay consistent across the whole project:
- Use the same naming
- Follow the same structure
- Apply the same patterns
Documentation#
Document everything that is not obvious:
1
2
3
4
5
6
7
| /// Gets the current volume of the audio device.
///
/// # Returns
/// The volume as a percentage (0-100)
pub fn get_volume() -> Result<u32> {
// Implementation
}
|
1
2
3
4
5
6
7
| /**
* Handles the user's volume change.
* @param newLevel - New volume level (0-100)
*/
function handleVolumeChange(newLevel: number) {
// Implementation
}
|
Explicit errors#
Handle errors explicitly:
1
2
3
4
5
6
| // ❌ Doing nothing with the error
let file = std::fs::read_to_string("config.toml");
// ✅ Explicit handling
let file = std::fs::read_to_string("config.toml")
.map_err(|e| format!("Error reading config: {}", e))?;
|
TypeScript/JavaScript style guide#
Naming#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // Constants: UPPER_SNAKE_CASE
const MAX_VOLUME = 100;
const DEFAULT_THEME = 'dark';
// Variables/functions: camelCase
let currentVolume = 50;
function handleVolumeChange() { }
// Classes/interfaces/types: PascalCase
class AudioManager { }
interface Device { }
type Status = 'active' | 'inactive';
// Component files: PascalCase
// AudioControl.vue
// UserCard.vue
// Utility files: camelCase
// audioHelper.ts
// deviceManager.ts
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // Indentation: 2 spaces
const config = {
name: 'app',
settings: {
theme: 'dark'
}
};
// Quotes: single for strings
const greeting = 'Hello, world';
// Semicolons: always
const value = 42;
const name = 'test';
// Spaces: around operators
let result = a + b; // ✅
let result = a+b; // ❌
// Braces: same line
if (condition) { // ✅
// code
}
if (condition) // ❌
{
// code
}
|
Vue components#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
| <template>
<!-- Use v-if/v-show appropriately -->
<div v-if="isVisible" class="component">
<!-- One per line where possible -->
<button
@click="handleClick"
class="btn btn-primary"
:disabled="isLoading"
>
Click me
</button>
</div>
</template>
<script setup lang="ts">
// Import in order: external, internal, types
import { ref, computed, onMounted } from 'vue';
import { invoke } from '@tauri-apps/api/tauri';
import Button from '@/components/buttons/Button.vue';
import type { Device } from '@/interfaces/device';
// Declare props and emits at the start
interface Props {
title: string;
disabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
disabled: false
});
// Reactive variables
const isLoading = ref(false);
const data = ref<Device[]>([]);
// Computed properties
const isActive = computed(() => !isLoading.value);
// Methods
function handleClick() {
isLoading.value = true;
// logic
}
// Lifecycle hooks at the end
onMounted(() => {
// Load data
});
</script>
<style scoped>
/* Use classes over inline styles */
.component {
display: flex;
flex-direction: column;
}
/* Group related styles */
.btn {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
}
.btn-primary {
background-color: #007bff;
color: white;
}
</style>
|
Error handling#
1
2
3
4
5
6
7
8
9
10
11
12
| // ✅ Explicit handling
try {
const volume = await invoke('get_volume') as number;
handleVolumeUpdate(volume);
} catch (error) {
console.error('Error getting the volume:', error);
showErrorNotification('Could not get the volume');
}
// ❌ Ignoring errors
const volume = await invoke('get_volume');
handleVolumeUpdate(volume); // What if it fails?
|
Rust style guide#
Naming#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // Constants: UPPER_SNAKE_CASE
const MAX_RETRIES: u32 = 3;
const DEFAULT_TIMEOUT_MS: u64 = 5000;
// Variables/functions: snake_case
let current_volume = 50;
fn get_device_list() { }
// Structs/Enums/Traits: PascalCase
struct AudioDevice { }
enum DeviceStatus { }
trait DeviceManager { }
// Modules: snake_case
mod audio_service;
mod dbus_service;
// Files: snake_case
// audio_service.rs
// dbus_handler.rs
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| // Indentation: 4 spaces
fn example() {
let value = 42;
if condition {
// code
}
}
// Maximum line length: ~100 characters
let long_name = function_with_many_arguments(
arg1,
arg2,
arg3,
);
// Documentation: document public functions
/// Gets the current volume of the device.
///
/// # Returns
/// Volume as a percentage (0-100)
///
/// # Errors
/// Returns an error if D-Bus is not available
pub fn get_volume() -> Result<u32> {
// implementation
}
// Error handling: use ?
pub fn process() -> Result<()> {
let value = get_value()?;
let result = transform(value)?;
Ok(result)
}
|
Module structure#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| // Use clippy for linting
#![allow(dead_code)] // If necessary
// Imports at the start
use std::collections::HashMap;
use zbus::Connection;
use crate::error::{Error, Result};
use crate::structs::Device;
// Private types
pub struct AudioService {
connection: Connection,
}
// Implementation in this order:
impl AudioService {
// Constructor
pub fn new(connection: Connection) -> Self {
// ...
}
// Public methods
pub fn get_volume(&self) -> Result<u32> {
// ...
}
// Private methods
fn validate_input(&self) -> Result<()> {
// ...
}
}
|
Rust error handling#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // ✅ Use Result<T>
pub fn set_volume(level: u32) -> Result<()> {
if level > 100 {
return Err(Error::InvalidVolume);
}
// implementation
Ok(())
}
// ✅ Use the ? operator
pub fn process() -> Result<()> {
let value = get_value()?; // Propagates the error
Ok(value)
}
// ✅ Use match for complex cases
match operation() {
Ok(result) => println!("Success: {}", result),
Err(e) => eprintln!("Error: {}", e),
}
|
Testing#
TypeScript/Vue#
1
2
3
4
5
6
7
8
9
10
11
12
13
| // Name tests clearly
describe('AudioControl', () => {
it('should increase volume when plus button is clicked', () => {
// arrange
const wrapper = mount(AudioControl);
// act
wrapper.find('.btn-plus').trigger('click');
// assert
expect(wrapper.vm.volume).toBe(51);
});
});
|
Rust#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_volume_validation() {
// Arrange
let invalid_volume = 150;
// Act & Assert
assert!(validate_volume(invalid_volume).is_err());
}
#[tokio::test]
async fn test_get_volume() {
// For async tests
let result = get_volume().await;
assert!(result.is_ok());
}
}
|
TypeScript#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // ❌ Unnecessary re-rendering
<div v-for="item in items" :key="index">
{{ item }}
</div>
// ✅ Use unique IDs as the key
<div v-for="item in items" :key="item.id">
{{ item.name }}
</div>
// ❌ A computed that is always recalculated
const filtered = computed(() => {
return items.value.filter(/* expensive operation */);
});
// ✅ Cache the results
const filtered = computed(() => {
if (!needsRefresh.value) return cached.value;
cached.value = items.value.filter(/* expensive operation */);
return cached.value;
});
|
Rust#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // ❌ Unnecessary cloning
fn process(items: Vec<Item>) {
let copy = items.clone(); // Why?
transform(copy);
}
// ✅ Use references
fn process(items: &[Item]) {
transform(items);
}
// ❌ Unnecessary allocations
pub fn get_names() -> Vec<String> {
vec!["a".to_string(), "b".to_string()]
}
// ✅ Use Cow for flexible cases
pub fn get_names() -> Vec<&'static str> {
vec!["a", "b"]
}
|
Commits and versioning#
Commit messages#
Use the Conventional Commits format:
1
2
3
4
5
| <type>(<scope>): <description>
<body>
<footer>
|
Types:
feat: New functionalityfix: Bug fixdocs: Documentation changesstyle: Formatting changesrefactor: Refactoring with no functional changeperf: Performance improvementtest: Adding testschore: Configuration changes
Examples:
1
2
3
4
5
6
7
| feat(audio): add support for volume normalization
Implement automatic volume normalization across
different audio devices to provide consistent
output levels.
Fixes #1234
|
1
2
3
4
5
6
| fix(network): resolve WiFi disconnect issue
Changed connection retry logic to use exponential
backoff instead of fixed intervals.
Closes #5678
|
Rust#
1
2
3
4
5
6
7
8
9
10
| # Run clippy for warnings
cargo clippy
# Format the code
cargo fmt
# Check before committing
cargo check
cargo fmt --check
cargo clippy -- -D warnings
|
TypeScript#
1
2
3
4
5
6
7
8
| # ESLint
npm run lint
# Prettier (automatic formatting)
npx prettier --write src/
# TypeScript check
npx tsc --noEmit
|
Code review#
PR checklist#
When reviewing code#
- Clarity: is it easy to understand?
- Correctness: does it do what it intends to?
- Style: does it follow the guidelines?
- Testing: is it well tested?
- Performance: is it efficient?
Useful resources#