🔍 현재 상황 분석
더보기 효율 화면은 플레이어들이 어떤 레이드의 "더보기 보상"의 효율을 한눈에 볼 수 있도록 도와주는 서비스입니다. 하지만 현재 효율성 분석 페이지에는 몇 가지 심각한 문제점들이 있었습니다
🚨 발견된 문제점들
1. 가독성이 너무 떨어진다
현재 화면에는 이런 식으로 정보가 표시됩니다:

사용자 입장에서는 "어떤 보상을 얼마나 받는지", "효율성이 얼마나 되는지" 직관적으로 파악하기 어려웠습니다
2. 어떤 보상을 얼마만큼 얻었는지 확인할 수 없다
위의 텍스트에서는 "더보기 보상 골드: 1234.5"라고만 나오지, 실제로 어떤 재료를 몇 개씩 받는지 알 수 없었습니다.
3. 레이드별 특수 보상 획득량을 알 수 없다
Lost Ark의 각 레이드는 고유한 특수 아이템들을 제공하는데, 이런 정보가 전혀 표시되지 않았습니다
🔧 기존 코드 분석
기존 코드의 구조
// calcEfficiency.js - 기존 코드
function calculateAdditionalReward(raid, itemData, reward) {
return raid.additionalGold.map((gold, k) => {
let additionalRewardPrice = Object.keys(reward).reduce(
(sum, key) => sum + calcPrice(key, itemData, raid.additionalReward[k][Object.keys(reward).indexOf(key)]),
0
);
// 🚨 문제: 모든 정보를 하나의 문자열로 반환
return `${raid.RaidDifficulty} ${k + 1}관문 더보기 골드: ${gold}G 더보기 보상 골드: ${additionalRewardPrice}`;
});
}
function calcEfficiency(raidList, itemData) {
return raidList.map(raidName => {
let raids = Raid.filter(x => x.RaidName === raidName);
let reward = filteringItem(raids[0]?.RaidItemLevel || 0);
// 🚨 문제: 모든 난이도가 배열에 섞여서 반환
return raids.flatMap(raid => calculateAdditionalReward(raid, itemData, reward));
});
}
기존 구조의 문제점들
1. 문자열 기반 데이터 반환
// 반환되는 데이터 예시
[
[
"hard 1관문 더보기 골드: 2700G 더보기 보상 골드: 1234.5",
"hard 2관문 더보기 골드: 4100G 더보기 보상 골드: 2345.6",
"normal 1관문 더보기 골드: 2400G 더보기 보상 골드: 987.3"
]
]
- UI에서 이 문자열을 파싱해야 함
- 새로운 정보를 추가하려면 문자열 형식을 변경해야 함
- 정렬이나 필터링이 어려움
2. 난이도별 구분 부족
모든 난이도(하드, 노말, 싱글)가 하나의 배열에 섞여 있어서, UI에서 탭으로 나누기 어려웠습니다.
3. 특수 보상 정보 누락
// 기존 코드에서는 이 정보들이 무시됨
"clearUniqueRewards": [
{ "우뢰의 뇌옥": 3, "특수 재련 : 순환 돌파석": 0 }
],
"additionalUniqueRewards": [
{ "우뢰의 뇌옥": 3, "특수 재련 : 순환 돌파석": 0 }
]
4. 복잡한 예외 처리
// 하드코딩된 예외 처리
const specialRaids = {
'에키드나': [...],
'카멘': [...],
'아브렐슈드': [...]
};
🎯 해결 방안: 새로운 데이터 구조 설계
설계 원칙
- 구조화된 데이터: 문자열 대신 객체로 정보 전달
- 명확한 분리: 난이도별, 관문별 정보를 명확히 구분
- 완전한 정보: 특수 보상까지 모든 정보 포함
- 확장 가능성: 새로운 레이드 추가 시 코드 수정 불필요
💡 핵심 개선사항 구현
1. 구조화된 데이터 매핑
기존의 하드코딩된 구조를 개선했습니다:
// raidDataProcessor.js - 새로운 구조
const MATERIAL_MAPPING = {
1640: {
destruction: '운명의 파괴석',
guardian: '운명의 수호석',
fragment: '운명의 파편',
breakthrough: '운명의 돌파석'
},
1580: {
destruction: '정제된 파괴강석',
guardian: '정제된 수호강석',
fragment: '명예의 파편',
breakthrough: '찬란한 명예의 돌파석'
},
// ... 다른 레벨들
};
function getMaterialTypes(raidItemLevel) {
const levels = Object.keys(MATERIAL_MAPPING).map(Number).sort((a, b) => b - a);
const targetLevel = levels.find(level => raidItemLevel >= level) || 0;
return MATERIAL_MAPPING[targetLevel];
}
개선점:
- 레이드 레벨에 따른 재료 타입 자동 결정
- 새로운 재료 추가 시 매핑만 수정하면 됨
- 코드의 가독성 향상
2. 관문별 상세 정보 구조화
function processGateDetails(raidData, itemData, materialTypes) {
return raidData.additionalGold.map((goldCost, gateIndex) => {
// 🔧 기본 재료 보상 계산
const materials = {};
const materialKeys = Object.keys(materialTypes);
materialKeys.forEach((key, index) => {
const materialName = materialTypes[key];
const quantity = raidData.additionalReward[gateIndex][index] || 0;
materials[key] = {
name: materialName,
quantity: quantity,
price: calculateMaterialPrice(materialName, itemData, quantity)
};
});
// 🔧 특수 보상 처리 (기존에 누락되었던 부분!)
const specialRewards = {};
const uniqueRewards = raidData.additionalUniqueRewards[gateIndex] || {};
Object.entries(uniqueRewards).forEach(([itemName, quantity]) => {
if (quantity > 0) {
specialRewards[itemName] = {
name: itemName,
quantity: quantity,
price: 0 // 특수 아이템은 가격 정보가 없으므로 0
};
}
});
// 🔧 효율성 자동 계산
const totalMaterialPrice = Object.values(materials).reduce(
(sum, material) => sum + material.price, 0
);
return {
gate: gateIndex + 1,
goldCost: goldCost,
materials: materials,
specialRewards: specialRewards,
totalMaterialPrice: totalMaterialPrice,
efficiency: totalMaterialPrice > 0 ? (totalMaterialPrice / goldCost * 100).toFixed(1) : 0
};
});
}
개선점:
- 각 관문별로 상세한 정보 제공
- 재료별 수량과 가격 정보 분리
- 특수 보상 정보 완전 포함
- 효율성 자동 계산
3. 난이도별 데이터 분리
function getStructuredRaidData(raidName, itemData) {
const raidVariants = Raid.filter(raid =>
raid.RaidName === raidName ||
raid.RaidName.includes(raidName) ||
raidName.includes(raid.RaidName.split('(')[0])
);
// 🔧 난이도별로 그룹화
const difficulties = {};
const availableDifficulties = [];
raidVariants.forEach(raidData => {
const difficulty = raidData.RaidDifficulty;
const materialTypes = getMaterialTypes(raidData.RaidItemLevel);
difficulties[difficulty] = {
itemLevel: raidData.RaidItemLevel,
gates: processGateDetails(raidData, itemData, materialTypes),
overallEfficiency: 0 // 나중에 계산
};
if (!availableDifficulties.includes(difficulty)) {
availableDifficulties.push(difficulty);
}
});
// 🔧 기본 난이도 설정 (우선순위: hard > normal > single)
const difficultyPriority = ['hard', 'normal', 'single'];
const defaultDifficulty = difficultyPriority.find(diff =>
availableDifficulties.includes(diff)
) || availableDifficulties[0];
return {
raidName,
difficulties,
availableDifficulties: availableDifficulties.sort((a, b) => {
const order = { 'hard': 1, 'normal': 2, 'single': 3 };
return (order[a] || 999) - (order[b] || 999);
}),
defaultDifficulty
};
}
개선점:
- 난이도별 명확한 분리
- 사용 가능한 난이도 자동 감지
- 기본 난이도 자동 설정
- 확장 가능한 구조
📊 Before vs After
🔴 Before (기존 구조)
// 반환되는 데이터
[
[
"hard 1관문 더보기 골드: 2700G 더보기 보상 골드: 1234.5",
"hard 2관문 더보기 골드: 4100G 더보기 보상 골드: 2345.6",
"hard 3관문 더보기 골드: 5800G 더보기 보상 골드: 3456.7",
"normal 1관문 더보기 골드: 2400G 더보기 보상 골드: 987.3"
]
]
문제점:
- ❌ 문자열 파싱 필요
- ❌ 난이도별 구분 어려움
- ❌ 특수 보상 정보 없음
- ❌ UI 컴포넌트에서 활용 제한적
- ❌ 효율성 계산을 별도로 해야 함
🟢 After (새로운 구조)
// 반환되는 데이터
[
{
raidName: "모르둠(3막)",
availableDifficulties: ["hard", "normal"],
defaultDifficulty: "hard",
difficulties: {
hard: {
itemLevel: 1700,
overallEfficiency: "142.3",
gates: [
{
gate: 1,
goldCost: 2700,
totalMaterialPrice: 1234.5,
efficiency: "45.7",
materials: {
destruction: {
name: "운명의 파괴석",
quantity: 830,
price: 128.65
},
guardian: {
name: "운명의 수호석",
quantity: 1660,
price: 136.12
},
fragment: {
name: "운명의 파편",
quantity: 7000,
price: 1.98
}
},
specialRewards: {
"우뢰의 뇌옥": {
name: "우뢰의 뇌옥",
quantity: 3,
price: 0
}
}
}
// ... 다른 관문들
]
},
normal: {
// ... 노말 난이도 데이터
}
}
}
]
개선점:
- ✅ 구조화된 객체 데이터
- ✅ 난이도별 명확한 분리
- ✅ 특수 보상 정보 완전 포함
- ✅ UI에서 바로 활용 가능
- ✅ 효율성 계산 자동화
- ✅ 확장 가능한 구조
🎓 배운 점들
1. 데이터 구조의 중요성
처음에는 "일단 동작하게 만들자"는 생각으로 문자열 기반으로 구현했지만, 기능이 복잡해질수록 한계가 드러났습니다. 처음부터 확장 가능한 구조를 고민하는 것이 중요하다는 것을 깨달았습니다.
2. 사용자 관점에서 생각하기
개발자 입장에서는 "동작하면 되지"라고 생각할 수 있지만, 사용자가 실제로 어떤 정보를 원하는지 고민해야 합니다.
3. 테스트의 중요성
새로운 구조를 만들 때 테스트 코드를 함께 작성하면 검증과 디버깅이 훨씬 쉬워집니다.
'LostBoard 프로젝트' 카테고리의 다른 글
| [LostBoard 프로젝트] 더보기 효율 화면 수정 - 2. 컴포넌트 분리 및 인터렉티브 UI 수정 (0) | 2025.09.30 |
|---|---|
| [LostBoard 프로젝트] 현황판 화면 디자인 수정 (0) | 2025.09.21 |
| [LostBoard 프로젝트] 초기화면 디자인 수정 (0) | 2025.09.16 |
| [LostBoard 프로젝트] 초기 완성본 (0) | 2025.02.12 |
| [LostBoard 프로젝트] API를 활용하여 데이터 받아오기 (0) | 2025.01.08 |