docs: 重新组织文档结构,按开发阶段分类
新的目录结构: 01-项目入门/ # 新人必读,项目基础 02-开发规范/ # 编码标准和规范 03-技术实现/ # 具体开发指导 04-高级开发/ # 进阶开发技巧 05-部署运维/ # 发布和部署 06-功能模块/ # 特定功能文档 新增导航文档: - docs/README.md - 完整的文档导航和使用指南 - 各目录下的README.md - 分类说明和使用指导 优化效果: - 开发者可以按阶段快速定位需要的文档 - 新人有清晰的学习路径 - 不同角色有针对性的文档推荐 - 提供了问题导向的快速查找功能
This commit is contained in:
406
docs/02-开发规范/开发哲学与最佳实践.md
Normal file
406
docs/02-开发规范/开发哲学与最佳实践.md
Normal file
@@ -0,0 +1,406 @@
|
||||
# 开发哲学与最佳实践
|
||||
|
||||
本文档阐述了WhaleTown项目的开发哲学和编程最佳实践,旨在指导团队创造高质量、可维护的代码。
|
||||
|
||||
## 🧘 开发哲学
|
||||
|
||||
### 核心理念
|
||||
- **用户体验至上** - 每个功能都要考虑用户感受
|
||||
- **代码即文档** - 代码应该自解释,清晰易懂
|
||||
- **简洁胜于复杂** - 优先选择简单直接的解决方案
|
||||
- **质量重于速度** - 宁可慢一点,也要做对
|
||||
|
||||
### 设计原则
|
||||
|
||||
#### 1. 流畅体验 (Juice or Death)
|
||||
每个用户交互都必须有视觉反馈和动画效果:
|
||||
|
||||
```gdscript
|
||||
# ✅ 正确:为UI交互添加动画
|
||||
func show_dialog() -> void:
|
||||
dialog.modulate.a = 0.0
|
||||
dialog.scale = Vector2(0.8, 0.8)
|
||||
dialog.visible = true
|
||||
|
||||
var tween = create_tween()
|
||||
tween.parallel().tween_property(dialog, "modulate:a", 1.0, 0.3)
|
||||
tween.parallel().tween_property(dialog, "scale", Vector2.ONE, 0.3)
|
||||
tween.set_ease(Tween.EASE_OUT)
|
||||
tween.set_trans(Tween.TRANS_BACK)
|
||||
|
||||
# ❌ 错误:没有动画的生硬切换
|
||||
func show_dialog() -> void:
|
||||
dialog.visible = true # 突然出现,体验差
|
||||
```
|
||||
|
||||
#### 2. 零魔法数字 (Zero Magic Numbers)
|
||||
所有数值都应该有明确的含义和来源:
|
||||
|
||||
```gdscript
|
||||
# ✅ 正确:使用导出变量或配置文件
|
||||
@export var move_speed: float = 200.0
|
||||
@export var jump_height: float = 400.0
|
||||
@export var health_max: int = 100
|
||||
|
||||
# 或从配置文件加载
|
||||
const CONFIG_PATH = "res://data/player_config.json"
|
||||
var config_data: Dictionary
|
||||
|
||||
func _ready() -> void:
|
||||
config_data = load_config(CONFIG_PATH)
|
||||
move_speed = config_data.get("move_speed", 200.0)
|
||||
|
||||
# ❌ 错误:硬编码的魔法数字
|
||||
func _physics_process(delta: float) -> void:
|
||||
velocity.x = input_direction.x * 200 # 200是什么?
|
||||
if position.y > 1000: # 1000代表什么?
|
||||
respawn()
|
||||
```
|
||||
|
||||
#### 3. 函数单一职责
|
||||
每个函数只做一件事,做好一件事:
|
||||
|
||||
```gdscript
|
||||
# ✅ 正确:职责分离
|
||||
func handle_player_input() -> void:
|
||||
var input_direction = get_input_direction()
|
||||
apply_movement(input_direction)
|
||||
check_interaction_input()
|
||||
|
||||
func get_input_direction() -> Vector2:
|
||||
return Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
||||
|
||||
func apply_movement(direction: Vector2) -> void:
|
||||
velocity = direction * move_speed
|
||||
move_and_slide()
|
||||
|
||||
func check_interaction_input() -> void:
|
||||
if Input.is_action_just_pressed("interact"):
|
||||
try_interact()
|
||||
|
||||
# ❌ 错误:一个函数做太多事
|
||||
func handle_everything() -> void:
|
||||
# 处理输入
|
||||
var direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
||||
# 处理移动
|
||||
velocity = direction * move_speed
|
||||
move_and_slide()
|
||||
# 处理交互
|
||||
if Input.is_action_just_pressed("interact"):
|
||||
# 检查交互对象
|
||||
var interactables = get_nearby_interactables()
|
||||
# 执行交互
|
||||
for obj in interactables:
|
||||
obj.interact()
|
||||
# 更新UI
|
||||
update_health_bar()
|
||||
# 播放音效
|
||||
play_footstep_sound()
|
||||
```
|
||||
|
||||
#### 4. 隐藏复杂性
|
||||
复杂的逻辑应该被封装,对外提供简洁的接口:
|
||||
|
||||
```gdscript
|
||||
# ✅ 正确:封装复杂逻辑
|
||||
class_name NetworkManager
|
||||
|
||||
func login(username: String, password: String, callback: Callable) -> int:
|
||||
return _make_request("POST", "/auth/login", {
|
||||
"username": username,
|
||||
"password": password
|
||||
}, callback)
|
||||
|
||||
func _make_request(method: String, endpoint: String, data: Dictionary, callback: Callable) -> int:
|
||||
# 复杂的网络请求逻辑被隐藏
|
||||
var request = HTTPRequest.new()
|
||||
var request_id = _generate_request_id()
|
||||
|
||||
# 设置请求头、处理认证、错误重试等复杂逻辑
|
||||
_setup_request_headers(request)
|
||||
_handle_authentication(request)
|
||||
_setup_retry_logic(request, callback)
|
||||
|
||||
return request_id
|
||||
|
||||
# 使用时非常简洁
|
||||
func _on_login_button_pressed() -> void:
|
||||
NetworkManager.login(username_input.text, password_input.text, _on_login_response)
|
||||
```
|
||||
|
||||
## 📋 编码最佳实践
|
||||
|
||||
### 1. 类型安全
|
||||
始终使用严格的类型声明:
|
||||
|
||||
```gdscript
|
||||
# ✅ 正确:明确的类型声明
|
||||
var player_health: int = 100
|
||||
var move_speed: float = 200.0
|
||||
var player_name: String = "Player"
|
||||
var inventory_items: Array[Item] = []
|
||||
var config_data: Dictionary = {}
|
||||
|
||||
func calculate_damage(base_damage: int, multiplier: float) -> int:
|
||||
return int(base_damage * multiplier)
|
||||
|
||||
# ❌ 错误:缺少类型信息
|
||||
var health = 100 # 类型不明确
|
||||
var speed = 200 # 可能是int也可能是float
|
||||
|
||||
func calculate_damage(base, mult): # 参数类型不明确
|
||||
return base * mult # 返回类型不明确
|
||||
```
|
||||
|
||||
### 2. 错误处理
|
||||
主动处理可能的错误情况:
|
||||
|
||||
```gdscript
|
||||
# ✅ 正确:完善的错误处理
|
||||
func load_save_file(file_path: String) -> Dictionary:
|
||||
if not FileAccess.file_exists(file_path):
|
||||
push_warning("存档文件不存在: " + file_path)
|
||||
return {}
|
||||
|
||||
var file = FileAccess.open(file_path, FileAccess.READ)
|
||||
if file == null:
|
||||
push_error("无法打开存档文件: " + file_path)
|
||||
return {}
|
||||
|
||||
var json_string = file.get_as_text()
|
||||
file.close()
|
||||
|
||||
if json_string.is_empty():
|
||||
push_warning("存档文件为空: " + file_path)
|
||||
return {}
|
||||
|
||||
var json = JSON.new()
|
||||
var parse_result = json.parse(json_string)
|
||||
if parse_result != OK:
|
||||
push_error("存档文件JSON格式错误: " + file_path)
|
||||
return {}
|
||||
|
||||
return json.data
|
||||
|
||||
# ❌ 错误:没有错误处理
|
||||
func load_save_file(file_path: String) -> Dictionary:
|
||||
var file = FileAccess.open(file_path, FileAccess.READ)
|
||||
var json_string = file.get_as_text()
|
||||
file.close()
|
||||
var json = JSON.new()
|
||||
json.parse(json_string)
|
||||
return json.data # 任何步骤出错都会崩溃
|
||||
```
|
||||
|
||||
### 3. 资源管理
|
||||
及时释放不需要的资源:
|
||||
|
||||
```gdscript
|
||||
# ✅ 正确:资源管理
|
||||
class_name AudioManager
|
||||
|
||||
var audio_players: Array[AudioStreamPlayer] = []
|
||||
var max_concurrent_sounds: int = 10
|
||||
|
||||
func play_sound(sound: AudioStream, volume: float = 0.0) -> void:
|
||||
# 清理已完成的音频播放器
|
||||
_cleanup_finished_players()
|
||||
|
||||
# 限制并发音频数量
|
||||
if audio_players.size() >= max_concurrent_sounds:
|
||||
_stop_oldest_player()
|
||||
|
||||
var player = AudioStreamPlayer.new()
|
||||
add_child(player)
|
||||
player.stream = sound
|
||||
player.volume_db = volume
|
||||
player.finished.connect(_on_audio_finished.bind(player))
|
||||
player.play()
|
||||
|
||||
audio_players.append(player)
|
||||
|
||||
func _cleanup_finished_players() -> void:
|
||||
audio_players = audio_players.filter(func(player): return player.playing)
|
||||
|
||||
func _on_audio_finished(player: AudioStreamPlayer) -> void:
|
||||
audio_players.erase(player)
|
||||
player.queue_free()
|
||||
```
|
||||
|
||||
### 4. 性能优化
|
||||
编写高效的代码:
|
||||
|
||||
```gdscript
|
||||
# ✅ 正确:性能优化的代码
|
||||
class_name EnemyManager
|
||||
|
||||
var enemies: Array[Enemy] = []
|
||||
var update_timer: float = 0.0
|
||||
const UPDATE_INTERVAL: float = 0.1 # 每100ms更新一次
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
update_timer += delta
|
||||
if update_timer >= UPDATE_INTERVAL:
|
||||
_update_enemies(update_timer)
|
||||
update_timer = 0.0
|
||||
|
||||
func _update_enemies(delta_time: float) -> void:
|
||||
# 只更新屏幕附近的敌人
|
||||
var camera_pos = get_viewport().get_camera_2d().global_position
|
||||
var screen_size = get_viewport().get_visible_rect().size
|
||||
|
||||
for enemy in enemies:
|
||||
if _is_enemy_near_screen(enemy, camera_pos, screen_size):
|
||||
enemy.update_ai(delta_time)
|
||||
|
||||
func _is_enemy_near_screen(enemy: Enemy, camera_pos: Vector2, screen_size: Vector2) -> bool:
|
||||
var distance = enemy.global_position.distance_to(camera_pos)
|
||||
var max_distance = screen_size.length() * 0.6 # 屏幕对角线的60%
|
||||
return distance <= max_distance
|
||||
|
||||
# ❌ 错误:性能问题
|
||||
func _process(delta: float) -> void:
|
||||
# 每帧更新所有敌人,无论是否可见
|
||||
for enemy in enemies:
|
||||
enemy.update_ai(delta) # 可能有数百个敌人
|
||||
# 每帧进行复杂计算
|
||||
var path = enemy.find_path_to_player()
|
||||
enemy.follow_path(path)
|
||||
```
|
||||
|
||||
## 🎯 代码审查标准
|
||||
|
||||
### 审查清单
|
||||
在提交代码前,请检查以下项目:
|
||||
|
||||
#### 功能性
|
||||
- [ ] 代码实现了预期功能
|
||||
- [ ] 处理了边界情况和错误情况
|
||||
- [ ] 添加了必要的测试用例
|
||||
|
||||
#### 可读性
|
||||
- [ ] 变量和函数名称清晰明确
|
||||
- [ ] 代码结构逻辑清晰
|
||||
- [ ] 添加了必要的注释
|
||||
|
||||
#### 性能
|
||||
- [ ] 避免了不必要的计算
|
||||
- [ ] 正确管理了资源生命周期
|
||||
- [ ] 使用了合适的数据结构
|
||||
|
||||
#### 规范性
|
||||
- [ ] 遵循了项目命名规范
|
||||
- [ ] 使用了正确的类型声明
|
||||
- [ ] 符合架构设计原则
|
||||
|
||||
### 代码示例评分
|
||||
|
||||
#### 优秀代码示例 (A级)
|
||||
```gdscript
|
||||
extends CharacterBody2D
|
||||
class_name Player
|
||||
|
||||
## 玩家角色控制器
|
||||
##
|
||||
## 负责处理玩家输入、移动和基础交互
|
||||
## 使用事件系统与其他组件通信
|
||||
|
||||
@export_group("Movement")
|
||||
@export var move_speed: float = 200.0
|
||||
@export var acceleration: float = 1000.0
|
||||
@export var friction: float = 800.0
|
||||
|
||||
@export_group("Interaction")
|
||||
@export var interaction_range: float = 50.0
|
||||
|
||||
@onready var sprite: Sprite2D = %Sprite2D
|
||||
@onready var animation_player: AnimationPlayer = %AnimationPlayer
|
||||
@onready var interaction_area: Area2D = %InteractionArea
|
||||
|
||||
var _current_interactable: Interactable = null
|
||||
|
||||
func _ready() -> void:
|
||||
_setup_interaction_area()
|
||||
_connect_signals()
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
_handle_movement(delta)
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("interact"):
|
||||
_try_interact()
|
||||
|
||||
func _handle_movement(delta: float) -> void:
|
||||
var input_direction := _get_movement_input()
|
||||
_apply_movement(input_direction, delta)
|
||||
_update_animation(input_direction)
|
||||
|
||||
func _get_movement_input() -> Vector2:
|
||||
return Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
||||
|
||||
func _apply_movement(direction: Vector2, delta: float) -> void:
|
||||
if direction != Vector2.ZERO:
|
||||
velocity = velocity.move_toward(direction * move_speed, acceleration * delta)
|
||||
else:
|
||||
velocity = velocity.move_toward(Vector2.ZERO, friction * delta)
|
||||
|
||||
move_and_slide()
|
||||
|
||||
func _update_animation(direction: Vector2) -> void:
|
||||
if direction.length() > 0.1:
|
||||
animation_player.play("walk")
|
||||
sprite.flip_h = direction.x < 0
|
||||
else:
|
||||
animation_player.play("idle")
|
||||
```
|
||||
|
||||
#### 需要改进的代码 (C级)
|
||||
```gdscript
|
||||
extends CharacterBody2D
|
||||
|
||||
var speed = 200
|
||||
var player
|
||||
var enemies = []
|
||||
|
||||
func _ready():
|
||||
player = self
|
||||
|
||||
func _process(delta):
|
||||
var dir = Vector2()
|
||||
if Input.is_action_pressed("ui_left"):
|
||||
dir.x -= 1
|
||||
if Input.is_action_pressed("ui_right"):
|
||||
dir.x += 1
|
||||
if Input.is_action_pressed("ui_up"):
|
||||
dir.y -= 1
|
||||
if Input.is_action_pressed("ui_down"):
|
||||
dir.y += 1
|
||||
|
||||
velocity = dir * speed
|
||||
move_and_slide()
|
||||
|
||||
for enemy in enemies:
|
||||
if position.distance_to(enemy.position) < 100:
|
||||
print("near enemy")
|
||||
```
|
||||
|
||||
## 🚀 持续改进
|
||||
|
||||
### 重构指导原则
|
||||
1. **小步快跑** - 每次只重构一小部分
|
||||
2. **测试保护** - 重构前确保有测试覆盖
|
||||
3. **功能不变** - 重构不改变外部行为
|
||||
4. **逐步优化** - 持续改进代码质量
|
||||
|
||||
### 技术债务管理
|
||||
```gdscript
|
||||
# 使用TODO注释标记技术债务
|
||||
# TODO: 重构这个函数,职责过多
|
||||
# FIXME: 这里有性能问题,需要优化
|
||||
# HACK: 临时解决方案,需要找到更好的方法
|
||||
# NOTE: 这里的逻辑比较复杂,需要详细注释
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**记住:优秀的代码不仅能工作,更要易于理解、维护和扩展。追求代码质量是每个开发者的责任!**
|
||||
Reference in New Issue
Block a user