# 程式碼規範與開發標準 ## 概述 建立統一的程式碼撰寫規範和開發流程標準,確保團隊協作效率和代碼品質。 ## 通用開發原則 ### 代碼品質原則 - [ ] **可讀性優先**: 代碼應該容易閱讀和理解 - [ ] **一致性**: 遵循統一的命名和格式規範 - [ ] **簡潔性**: 避免過度複雜的解決方案 - [ ] **可測試性**: 代碼結構便於單元測試 - [ ] **可維護性**: 考慮未來修改和擴展的便利性 ### SOLID原則遵循 - [ ] **單一職責**: 每個函數/類只負責一個明確的功能 - [ ] **開放封閉**: 對擴展開放,對修改封閉 - [ ] **里氏替換**: 子類應該能夠替換父類 - [ ] **介面隔離**: 不應該依賴不需要的介面 - [ ] **依賴倒置**: 依賴抽象而非具體實現 ## C# (.NET Core) 規範 ### 基本格式規則 #### EditorConfig 配置 ```ini # .editorconfig root = true [*] charset = utf-8 end_of_line = crlf insert_final_newline = true indent_style = space indent_size = 4 trim_trailing_whitespace = true [*.{cs,csx,vb,vbx}] indent_size = 4 insert_final_newline = true [*.{json,js,ts,tsx,css,scss,yml,yaml}] indent_size = 2 ``` #### .NET 分析器規則 ```xml net8.0 enable true CS1591 true all runtime; build; native; contentfiles; analyzers ``` ### 命名規範 #### C# 命名慣例 ```csharp // ✅ 類別和方法使用PascalCase public class UserService { public async Task GetUserProfileAsync(Guid userId) { // 方法實現 } public decimal CalculateMonthlyInterestRate(decimal principal, decimal rate) { return principal * rate / 12; } } // ✅ 變數和參數使用camelCase private readonly IUserRepository _userRepository; private const int MaxRetryAttempts = 3; public async Task ValidateUserAsync(string email, string password) { var isValidEmail = IsValidEmailFormat(email); var hashedPassword = HashPassword(password); return isValidEmail && await _userRepository.ValidateCredentialsAsync(email, hashedPassword); } // ❌ 避免的命名 private string data; // 太泛化 private int u; // 太簡短 private async Task GetUserProfileDataAsync() {} // 冗餘的Data後綴 ``` #### 常數和列舉 ```typescript // ✅ 常數使用SCREAMING_SNAKE_CASE const API_ENDPOINTS = { USER_PROFILE: '/api/v1/users/profile', DIALOGUE_START: '/api/v1/dialogues/start', } as const; const MAX_DIALOGUE_DURATION_MINUTES = 30; const DEFAULT_PAGINATION_LIMIT = 20; // ✅ 列舉使用PascalCase enum DialogueStatus { InProgress = 'in_progress', Completed = 'completed', Abandoned = 'abandoned', } enum UserSubscriptionPlan { Free = 'free', Basic = 'basic', Premium = 'premium', Professional = 'professional', } ``` #### 類型定義 ```typescript // ✅ 介面使用PascalCase,以I開頭(可選) interface UserProfile { userId: string; username: string; email: string; createdAt: Date; subscription: UserSubscriptionPlan; } interface ApiResponse { success: boolean; data: T | null; message?: string; error?: ApiError; } // ✅ 類型別名使用PascalCase type DialogueAnalysis = { grammarScore: number; semanticScore: number; fluencyScore: number; overallScore: number; feedback: string[]; }; type CreateDialogueRequest = { scenarioId: string; difficultyOverride?: string; targetVocabulary?: string[]; }; ``` ### 函數撰寫規範 #### 函數設計原則 ```typescript // ✅ 函數應該小巧、單一職責 const validateEmailFormat = (email: string): boolean => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; const calculateDialogueScore = ( grammarScore: number, semanticScore: number, fluencyScore: number ): number => { const weights = { grammar: 0.3, semantic: 0.4, fluency: 0.3 }; return Math.round( grammarScore * weights.grammar + semanticScore * weights.semantic + fluencyScore * weights.fluency ); }; // ✅ 使用純函數優於副作用函數 const createUserSlug = (username: string): string => { return username .toLowerCase() .replace(/[^a-z0-9]/g, '-') .replace(/-+/g, '-') .trim(); }; // ✅ 錯誤處理明確 const fetchUserProfile = async (userId: string): Promise => { try { const response = await api.get(`/users/${userId}`); if (!response.data) { throw new Error('User profile not found'); } return response.data; } catch (error) { logger.error('Failed to fetch user profile', { userId, error }); throw error; } }; ``` #### 異步處理規範 ```typescript // ✅ 使用async/await而非Promise.then const processDialogueAnalysis = async ( dialogueId: string ): Promise => { const dialogue = await getDialogue(dialogueId); const analysis = await analyzeDialogueWithAI(dialogue.messages); const savedAnalysis = await saveAnalysisResults(dialogueId, analysis); return savedAnalysis; }; // ✅ 適當的錯誤處理和重試機制 const retryOperation = async ( operation: () => Promise, maxRetries: number = 3, delayMs: number = 1000 ): Promise => { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { if (attempt === maxRetries) { throw error; } await new Promise(resolve => setTimeout(resolve, delayMs * attempt)); } } throw new Error('All retry attempts failed'); }; ``` ### React/React Native 組件規範 #### 組件結構 ```tsx // ✅ 組件檔案結構標準 import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { Button } from '@/components/ui'; import { DialogueService } from '@/services'; import { updateDialogueProgress } from '@/store/slices/dialogueSlice'; import type { Dialogue, DialogueMessage } from '@/types'; // ✅ Props介面定義 interface DialogueChatProps { dialogueId: string; onDialogueComplete: (dialogue: Dialogue) => void; isVisible: boolean; } // ✅ 組件主體 export const DialogueChat: React.FC = ({ dialogueId, onDialogueComplete, isVisible, }) => { // State declarations const [inputText, setInputText] = useState(''); const [isLoading, setIsLoading] = useState(false); // Redux selectors const dialogue = useAppSelector(state => state.dialogue.currentDialogue ); const dispatch = useAppDispatch(); // Effects useEffect(() => { if (isVisible && dialogueId) { loadDialogue(); } }, [isVisible, dialogueId]); // Handlers const handleSendMessage = useCallback(async () => { if (!inputText.trim()) return; setIsLoading(true); try { const response = await DialogueService.sendMessage(dialogueId, inputText); dispatch(updateDialogueProgress(response)); setInputText(''); } catch (error) { // Error handling } finally { setIsLoading(false); } }, [dialogueId, inputText, dispatch]); const loadDialogue = useCallback(async () => { // Load dialogue logic }, [dialogueId]); // Early returns if (!dialogue) { return ; } // Main render return ( {dialogue.scenarioTitle} {/* Component content */}