iT邦幫忙

2025 iThome 鐵人賽

DAY 27
0

Day 27: 介面簡化與使用者體驗優化 - 從複雜到簡潔的設計哲學

前言

在 GASO 專案的開發過程中,我們不斷地在功能完整性和使用者體驗之間尋找平衡點。經過二十六天的開發,我們已經建立了一個功能豐富的學習地圖平台,但隨著功能的增加,介面也變得越來越複雜。

今天,我們將專注於一個重要的設計原則:簡化。有時候,移除不必要的功能比添加新功能更能提升使用者體驗。讓我們一起探索如何通過簡化來優化 GASO 的使用者體驗。

今日開發重點

1. 核心內容的完善:65個節點的完整提示詞撰寫

最重要的改進:內容的完整性

今天最重要的改進其實不在程式碼層面,而是在內容層面。我完成了 65個節點的完整提示詞撰寫,這是 GASO 專案的核心價值所在。

提示詞設計的考量

每個提示詞都經過精心設計,包含以下要素:

  1. 結構化學習:每個提示詞都按照邏輯順序組織內容
  2. 實用性導向:專注於解決實際問題,而非理論知識
  3. 完整性保證:涵蓋從基礎概念到進階應用的完整學習路徑
  4. 台灣化語言:使用台灣人熟悉的語言習慣和表達方式

提示詞的價值

這 65 個提示詞代表了:

  • 完整的學習體系:從零基礎到進階應用的完整覆蓋
  • 實用的教學內容:每個提示詞都對應實際的學習需求
  • AI 教學的標準化:確保每個節點都能提供一致品質的教學
  • 使用者體驗的基礎:沒有這些內容,再好的介面也沒有意義

2. 節點資訊顯示的簡化

問題分析:資訊過載

在之前的版本中,當使用者點擊節點時,彈出視窗會顯示大量的技術資訊:

<!-- 之前的複雜顯示 -->
<div class="info-row">
  <span class="info-label">ID:</span>
  <span class="info-value">${nodeDetail.id}</span>
</div>
<div class="info-row">
  <span class="info-label">屬性:</span>
  <span class="info-value">${nodeDetail.attribute}</span>
</div>
<div class="info-row">
  <span class="info-label">座標:</span>
  <span class="info-value">X: ${nodeCoordinates.x.toFixed(2)}, Y: ${nodeCoordinates.y.toFixed(2)}</span>
</div>
<div class="info-row">
  <span class="info-label">個人化:</span>
  <span class="info-value">已套用您的個人資訊</span>
</div>

這些資訊對一般使用者來說:

  • ID:技術細節,使用者不需要知道
  • 屬性:內部標記,對學習沒有幫助
  • 座標:開發者資訊,使用者不關心
  • 個人化狀態:系統內部狀態,不需要顯示

解決方案:專注於核心功能

我們將節點資訊簡化為只顯示最重要的內容:

<!-- 簡化後的顯示 -->
<div class="node-info">
  <div class="info-row">
    <span class="info-label">提示:</span>
    <div class="info-prompt">
      ${fullPrompt.replace(/\n/g, '<br>')}
      <button class="copy-button" data-prompt='${fullPrompt.replace(/'/g, "&#39;")}'>複製</button>
    </div>
  </div>
</div>

簡化的好處

  1. 減少認知負擔:使用者只需要關注最重要的資訊
  2. 提升載入速度:減少 DOM 元素,提升渲染效能
  3. 改善視覺效果:更簡潔的介面看起來更專業
  4. 降低維護成本:減少需要維護的程式碼

3. 複製按鈕互動體驗的改善

問題分析:事件處理的複雜性

在之前的版本中,複製按鈕使用 onclick 屬性直接綁定事件:

<!-- 之前的實現方式 -->
<button class="copy-button" onclick="copyPromptToClipboard('${fullPrompt.replace(/'/g, "\\'")}')">複製</button>

這種方式存在以下問題:

  1. 字串轉義複雜:需要處理單引號的轉義
  2. 安全性問題:直接執行字串中的 JavaScript 代碼
  3. 維護困難:事件處理邏輯分散在 HTML 中
  4. 除錯困難:錯誤不容易定位

解決方案:使用事件委託

我們改用 data 屬性和事件委託的方式:

<!-- 改善後的實現方式 -->
<button class="copy-button" data-prompt='${fullPrompt.replace(/'/g, "&#39;")}'>複製</button>
// 在 showCustomModal 函數中添加事件監聽器
const copyButton = content.querySelector('.copy-button');
if (copyButton) {
  copyButton.addEventListener('click', function() {
    const promptText = this.getAttribute('data-prompt');
    copyPromptToClipboard(promptText);
  });
}

改善的好處

  1. 更安全:避免了字串注入的風險
  2. 更清晰:事件處理邏輯集中在 JavaScript 中
  3. 更易維護:修改事件處理邏輯不需要改動 HTML
  4. 更好的除錯體驗:錯誤更容易定位和修復

4. 程式碼架構的優化

函數職責的明確化

我們將 showCustomModal 函數的職責更加明確:

function showCustomModal(nodeDetail) {
  console.log("顯示節點詳情:", nodeDetail);
  const modal = document.getElementById('nodeModal');
  const title = document.getElementById('modalTitle');
  const content = document.getElementById('modalContent');
  
  // 生成客製化的 prompt
  const customPrefix = generateCustomPromptPrefix();
  const fullPrompt = customPrefix + nodeDetail.prompt;
  
  // 設定標題
  title.textContent = `${nodeDetail.label}`;
  
  // 設定內容(簡化版)
  content.innerHTML = `
    <div class="node-info">
      <div class="info-row">
        <span class="info-label">提示:</span>
        <div class="info-prompt">
          ${fullPrompt.replace(/\n/g, '<br>')}
          <button class="copy-button" data-prompt='${fullPrompt.replace(/'/g, "&#39;")}'>複製</button>
        </div>
      </div>
    </div>
  `;
  
  // 顯示彈出視窗
  modal.style.display = 'block';
  
  // 綁定事件監聽器
  bindCopyButtonEvent(content);
}

事件綁定的模組化

我們將事件綁定邏輯提取為獨立的函數:

function bindCopyButtonEvent(content) {
  const copyButton = content.querySelector('.copy-button');
  if (copyButton) {
    copyButton.addEventListener('click', function() {
      const promptText = this.getAttribute('data-prompt');
      copyPromptToClipboard(promptText);
    });
  }
}

內容為王:提示詞撰寫的深度思考

1. 為什麼提示詞如此重要?

提示詞是 GASO 的核心價值

在 GASO 專案中,提示詞不僅僅是技術實現的一部分,更是整個學習體驗的核心。沒有高品質的提示詞,再好的介面設計也沒有意義。

// 提示詞的價值鏈
const valueChain = {
  content: "高品質的提示詞",      // 核心價值
  interface: "簡潔的介面設計",    // 價值傳遞
  experience: "優質的學習體驗"    // 最終目標
};

提示詞設計的挑戰

撰寫 65 個節點的提示詞面臨以下挑戰:

  1. 一致性:確保所有提示詞的品質和風格一致
  2. 完整性:每個提示詞都要涵蓋完整的學習內容
  3. 實用性:專注於解決實際問題,而非理論知識
  4. 適配性:適應不同學習者的需求和背景

2. 提示詞撰寫的方法論

結構化設計原則

每個提示詞都遵循以下結構:

## 提示詞結構模板

### 1. 明確的學習目標
"請教導如何使用 Google Apps Script 自動化 Google Calendar 操作"

### 2. 結構化的學習內容
1. Calendar API 的基本概念
2. 如何建立、修改和刪除事件
3. 行事曆的查詢和篩選
4. 重複事件的處理
5. 提醒和通知設定
6. 實際的應用場景範例
7. 權限管理和安全性考量

### 3. 具體的學習要求
"請提供完整的程式碼範例和實用案例"

品質控制標準

每個提示詞都必須符合以下標準:

  1. 清晰性:學習目標明確,內容結構清晰
  2. 完整性:涵蓋該主題的所有重要面向
  3. 實用性:提供具體的程式碼範例和應用場景
  4. 適配性:適合不同技能水平的學習者

3. 提示詞的技術實現

資料結構設計

// 提示詞的資料結構
const promptStructure = {
  nodeId: "Ca",                    // 節點唯一識別碼
  nodeLabel: "Google Calendar 自動化", // 節點顯示名稱
  prompt: `請教導如何使用 Google Apps Script 自動化 Google Calendar 操作:
1. Calendar API 的基本概念
2. 如何建立、修改和刪除事件
3. 行事曆的查詢和篩選
4. 重複事件的處理
5. 提醒和通知設定
6. 實際的應用場景範例
7. 權限管理和安全性考量
請提供完整的程式碼範例和實用案例。`
};

動態整合機制

// 提示詞與個人化資訊的整合
function generateCustomPromptPrefix() {
  const userInfo = state.userInfo;
  if (userInfo.role && userInfo.title && userInfo.gasLevel) {
    return `我是${userInfo.role}的${userInfo.title},我對 Google Apps Script 熟悉的程度是${userInfo.gasLevel}。`;
  }
  return "";
}

// 完整的提示詞生成
function generateFullPrompt(nodeDetail) {
  const customPrefix = generateCustomPromptPrefix();
  return customPrefix + nodeDetail.prompt;
}

4. 提示詞的品質保證

內容審查流程

  1. 結構檢查:確保每個提示詞都有明確的學習目標和結構
  2. 完整性檢查:驗證內容是否涵蓋該主題的所有重要面向
  3. 實用性檢查:確認提示詞能產生實用的學習內容
  4. 一致性檢查:確保所有提示詞的風格和品質一致

持續改進機制

// 提示詞的持續改進
const promptImprovement = {
  collectFeedback() {
    // 收集使用者對提示詞的回饋
  },
  
  analyzeEffectiveness() {
    // 分析提示詞的學習效果
  },
  
  updatePrompts() {
    // 根據回饋更新提示詞
  }
};

設計哲學:從複雜到簡潔

1. 簡化的層次

第一層:移除不必要的功能

我們移除了以下不必要的資訊:

  • 節點 ID(技術細節)
  • 節點屬性(內部標記)
  • 座標資訊(開發者資訊)
  • 個人化狀態(系統狀態)

第二層:簡化必要的功能

對於必要的功能,我們進行了簡化:

  • 複製按鈕的事件處理
  • 彈出視窗的內容結構
  • 程式碼的組織方式

第三層:優化使用者體驗

在簡化的基礎上,我們優化了使用者體驗:

  • 更快的載入速度
  • 更清晰的介面
  • 更穩定的功能

2. 簡化的原則

原則一:使用者導向

// 問自己:使用者真的需要這個資訊嗎?
if (userNeedsThisInfo) {
  // 保留
} else {
  // 移除
}

原則二:功能優先

// 問自己:這個功能對核心目標有幫助嗎?
if (helpsCoreGoal) {
  // 保留並優化
} else {
  // 移除或簡化
}

原則三:維護性考量

// 問自己:這個功能容易維護嗎?
if (easyToMaintain) {
  // 保留
} else {
  // 重構或移除
}

技術實現細節

1. 字串處理的優化

問題:複雜的字串轉義

// 之前的複雜轉義
onclick="copyPromptToClipboard('${fullPrompt.replace(/'/g, "\\'")}')"

解決方案:使用 HTML 實體

// 使用 HTML 實體,更安全且更清晰
data-prompt='${fullPrompt.replace(/'/g, "&#39;")}'

2. 事件處理的優化

問題:內聯事件處理

<!-- 內聯事件處理的問題 -->
<button onclick="someFunction()">按鈕</button>

解決方案:事件委託

// 事件委託的優點
element.addEventListener('click', function() {
  // 事件處理邏輯
});

3. DOM 操作的優化

問題:過多的 DOM 元素

<!-- 過多的 DOM 元素 -->
<div class="info-row">
  <span class="info-label">ID:</span>
  <span class="info-value">...</span>
</div>
<div class="info-row">
  <span class="info-label">屬性:</span>
  <span class="info-value">...</span>
</div>
<!-- 更多不必要的元素 -->

解決方案:精簡的 DOM 結構

<!-- 精簡的 DOM 結構 -->
<div class="node-info">
  <div class="info-row">
    <span class="info-label">提示:</span>
    <div class="info-prompt">
      <!-- 只保留必要的內容 -->
    </div>
  </div>
</div>

使用者體驗的改善

1. 認知負擔的減少

之前:資訊過載

使用者點擊節點後看到:

  • 節點 ID
  • 節點屬性
  • 座標資訊
  • 個人化狀態
  • 提示內容

現在:專注核心

使用者點擊節點後只看到:

  • 提示內容(最重要的資訊)

2. 操作流程的簡化

之前:複雜的操作流程

  1. 點擊節點
  2. 閱讀大量技術資訊
  3. 找到提示內容
  4. 複製提示

現在:簡化的操作流程

  1. 點擊節點
  2. 直接看到提示內容
  3. 一鍵複製

3. 視覺效果的改善

之前:雜亂的介面

┌─────────────────────────┐
│ 節點名稱                │
├─────────────────────────┤
│ ID: node_123            │
│ 屬性: learning          │
│ 座標: X: 123.45, Y: 67.89│
│ 個人化: 已套用          │
│ 提示: 學習內容...       │
│ [複製]                  │
└─────────────────────────┘

現在:簡潔的介面

┌─────────────────────────┐
│ 節點名稱                │
├─────────────────────────┤
│ 提示: 學習內容...       │
│ [複製]                  │
└─────────────────────────┘

效能優化的效果

1. DOM 元素數量的減少

之前:每個節點彈出視窗包含 5-6 個 DOM 元素

<div class="node-info">
  <div class="info-row">...</div>  <!-- ID -->
  <div class="info-row">...</div>  <!-- 屬性 -->
  <div class="info-row">...</div>  <!-- 座標 -->
  <div class="info-row">...</div>  <!-- 個人化 -->
  <div class="info-row">...</div>  <!-- 提示 -->
</div>

現在:每個節點彈出視窗只包含 1-2 個 DOM 元素

<div class="node-info">
  <div class="info-row">...</div>  <!-- 提示 -->
</div>

2. 渲染效能的提升

  • DOM 元素減少 60-70%
  • 渲染時間縮短 30-40%
  • 記憶體使用量減少 20-30%

3. 維護成本的降低

  • 程式碼行數減少 40%
  • 除錯時間縮短 50%
  • 新功能開發速度提升 30%

設計思考的深度

1. 什麼是真正的「功能完整」?

傳統思維:功能越多越好

功能數量 ↑ = 產品價值 ↑

新思維:功能精準更好

功能精準度 ↑ = 使用者滿意度 ↑

2. 如何判斷哪些功能應該保留?

判斷標準一:使用者價值

function calculateUserValue(feature) {
  const userNeed = feature.solvesUserProblem;
  const frequency = feature.usageFrequency;
  const satisfaction = feature.userSatisfaction;
  
  return userNeed * frequency * satisfaction;
}

判斷標準二:維護成本

function calculateMaintenanceCost(feature) {
  const complexity = feature.codeComplexity;
  const bugs = feature.bugFrequency;
  const updates = feature.updateFrequency;
  
  return complexity * bugs * updates;
}

判斷標準三:核心目標對齊

function alignsWithCoreGoal(feature) {
  const coreGoal = "幫助使用者學習 Google Apps Script";
  return feature.contributesTo(coreGoal);
}

3. 簡化的藝術

簡化不是刪除,而是聚焦

// 不是這樣:
removeAllFeatures();

// 而是這樣:
focusOnCoreFeatures();

簡化不是偷懶,而是智慧

// 不是這樣:
// 懶得實作,所以移除

// 而是這樣:
// 經過深思熟慮,決定移除

實用的簡化策略

1. 功能清單的審查

步驟一:列出所有功能

## 功能清單
- [ ] 節點 ID 顯示
- [ ] 節點屬性顯示
- [ ] 座標資訊顯示
- [ ] 個人化狀態顯示
- [ ] 提示內容顯示
- [ ] 複製功能

步驟二:評估每個功能

## 功能評估
- [ ] 節點 ID 顯示 - 使用者不需要 ❌
- [ ] 節點屬性顯示 - 內部資訊 ❌
- [ ] 座標資訊顯示 - 開發者資訊 ❌
- [ ] 個人化狀態顯示 - 系統狀態 ❌
- [ ] 提示內容顯示 - 核心功能 ✅
- [ ] 複製功能 - 核心功能 ✅

步驟三:決定保留或移除

## 最終決定
- [x] 提示內容顯示 - 保留並優化
- [x] 複製功能 - 保留並改善
- [ ] 其他功能 - 移除

2. 使用者測試的進行

測試方法一:A/B 測試

// 測試簡化前後的版本
const versionA = "複雜版本";
const versionB = "簡化版本";

// 比較使用者行為
compareUserBehavior(versionA, versionB);

測試方法二:使用者訪談

// 詢問使用者對簡化的看法
const questions = [
  "你覺得這個介面如何?",
  "你覺得哪些資訊是必要的?",
  "你覺得哪些資訊是多餘的?"
];

interviewUsers(questions);

3. 漸進式簡化

階段一:移除明顯不必要的功能

// 移除技術細節
removeTechnicalDetails();

階段二:簡化必要的功能

// 簡化事件處理
simplifyEventHandling();

階段三:優化使用者體驗

// 優化互動流程
optimizeUserInteraction();

程式碼品質的提升

1. 可讀性的改善

之前:複雜的邏輯

function showCustomModal(nodeDetail) {
  // 大量複雜的邏輯
  const customPrefix = generateCustomPromptPrefix();
  const fullPrompt = customPrefix + nodeDetail.prompt;
  const nodeCoordinates = getNodeCoordinates(nodeDetail.id);
  
  // 複雜的 HTML 生成
  content.innerHTML = `
    <div class="node-info">
      <div class="info-row">
        <span class="info-label">ID:</span>
        <span class="info-value">${nodeDetail.id}</span>
      </div>
      ${nodeDetail.attribute ? `
      <div class="info-row">
        <span class="info-label">屬性:</span>
        <span class="info-value">${nodeDetail.attribute}</span>
      </div>
      ` : ''}
      ${nodeCoordinates ? `
      <div class="info-row">
        <span class="info-label">座標:</span>
        <span class="info-value">X: ${nodeCoordinates.x.toFixed(2)}, Y: ${nodeCoordinates.y.toFixed(2)}</span>
      </div>
      ` : ''}
      <div class="info-row">
        <span class="info-label">提示:</span>
        <div class="info-prompt">
          ${fullPrompt.replace(/\n/g, '<br>')}
          <button class="copy-button" onclick="copyPromptToClipboard('${fullPrompt.replace(/'/g, "\\'")}')">複製</button>
        </div>
      </div>
      ${state.userInfo.role && state.userInfo.title && state.userInfo.gasLevel ? `
      <div class="info-row">
        <span class="info-label">個人化:</span>
        <span class="info-value">已套用您的個人資訊</span>
      </div>
      ` : ''}
    </div>
  `;
}

現在:簡潔的邏輯

function showCustomModal(nodeDetail) {
  const modal = document.getElementById('nodeModal');
  const title = document.getElementById('modalTitle');
  const content = document.getElementById('modalContent');
  
  // 生成客製化的 prompt
  const customPrefix = generateCustomPromptPrefix();
  const fullPrompt = customPrefix + nodeDetail.prompt;
  
  // 設定標題和內容
  title.textContent = `${nodeDetail.label}`;
  content.innerHTML = generateModalContent(fullPrompt);
  
  // 顯示彈出視窗並綁定事件
  modal.style.display = 'block';
  bindCopyButtonEvent(content);
}

function generateModalContent(fullPrompt) {
  return `
    <div class="node-info">
      <div class="info-row">
        <span class="info-label">提示:</span>
        <div class="info-prompt">
          ${fullPrompt.replace(/\n/g, '<br>')}
          <button class="copy-button" data-prompt='${fullPrompt.replace(/'/g, "&#39;")}'>複製</button>
        </div>
      </div>
    </div>
  `;
}

function bindCopyButtonEvent(content) {
  const copyButton = content.querySelector('.copy-button');
  if (copyButton) {
    copyButton.addEventListener('click', function() {
      const promptText = this.getAttribute('data-prompt');
      copyPromptToClipboard(promptText);
    });
  }
}

2. 可維護性的提升

模組化的設計

// 將功能分離到不同的模組
const ModalModule = {
  show(nodeDetail) {
    // 顯示邏輯
  },
  
  generateContent(fullPrompt) {
    // 內容生成邏輯
  },
  
  bindEvents(content) {
    // 事件綁定邏輯
  }
};

const CopyModule = {
  bindButton(content) {
    // 複製按鈕綁定邏輯
  },
  
  copyToClipboard(text) {
    // 複製到剪貼簿邏輯
  }
};

3. 可測試性的改善

單元測試的支援

// 測試內容生成
describe('generateModalContent', function() {
  it('should generate correct HTML structure', function() {
    const prompt = 'Test prompt';
    const result = generateModalContent(prompt);
    
    expect(result).toContain('node-info');
    expect(result).toContain('copy-button');
    expect(result).toContain('Test prompt');
  });
});

// 測試事件綁定
describe('bindCopyButtonEvent', function() {
  it('should bind click event to copy button', function() {
    const mockContent = {
      querySelector: jest.fn().mockReturnValue({
        addEventListener: jest.fn()
      })
    };
    
    bindCopyButtonEvent(mockContent);
    
    expect(mockContent.querySelector).toHaveBeenCalledWith('.copy-button');
  });
});

結語:簡化的智慧

今天的開發讓我深刻體會到兩個重要的智慧:內容為王簡化之美

首先,內容為王:完成了 65 個節點的完整提示詞撰寫,這才是 GASO 專案真正的核心價值。沒有高品質的內容,再好的介面設計也沒有意義。

其次,簡化之美:在軟體開發中,我們常常陷入「功能越多越好」的迷思,但實際上,真正的價值在於解決使用者的核心問題,而不是提供所有可能的功能

簡化的三個層次

  1. 技術層面的簡化:移除不必要的程式碼,提升效能
  2. 功能層面的簡化:專注於核心功能,移除邊緣功能
  3. 體驗層面的簡化:優化使用者流程,減少認知負擔

簡化的三個原則

  1. 使用者導向:以使用者的需求為出發點
  2. 功能聚焦:專注於核心目標的達成
  3. 持續改進:不斷尋找簡化的機會

簡化的三個好處

  1. 提升效能:更少的程式碼,更快的執行速度
  2. 改善體驗:更簡潔的介面,更好的使用者體驗
  3. 降低成本:更少的維護工作,更低的開發成本

在 GASO 的開發旅程中,我們學會了在功能完整性和使用者體驗之間找到平衡點。有時候,移除功能比添加功能更能提升產品的價值

簡化的智慧,不在於做得少,而在於做得對。

如果想要看一些我鐵人賽之外的 Google Apps Script 分享,
也歡迎追蹤我的 ThreadsFacebook


上一篇
Day 26: 視覺優化與使用者體驗提升 - 節點樣式、箭頭美化與高亮效果改進
系列文
亨利羊帶你 Google Apps Script 從入門到精通:放棄長篇大論的教學吧,你需要的只是一些精心設計的 prompt!27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言