-
Notifications
You must be signed in to change notification settings - Fork 14
/
index.html
202 lines (178 loc) · 8.47 KB
/
index.html
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>Don't build (or build) that feature</title>
<meta name="description" content="A simple, opinionated decision system to help decide whether to build a software feature or not." />
<link rel="shortcut icon" href="https://dont.build/favicon.png" />
<meta property="og:image" content="https://dont.build/thumb.png">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="style.css?v=2" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="container">
<section class="box">
<p class="help">1: Low, 10: High</p>
<h1 class="title">Don't build (or build) that feature</h1>
<p>A simple, opinionated tool for helping decide whether a software feature is worth building.</p>
<br />
<form class="form">
<fieldset class="p-users">
<span class="value"></span>
<label>What proportion of users (1 = a small proportion, 10 = all users) are likely to use the feature?</label>
<input type="range" min="1" max="10" step="0.5" value="5.5" />
</fieldset>
<fieldset class="p-effort">
<span class="value"></span>
<label>What is the time, technical effort, and cost to build the feature?</label>
<input type="range" min="1" max="10" step="0.5" value="5.5" />
</fieldset>
<fieldset class="p-user-value">
<span class="value"></span>
<label>How important are the target users to the existing product?</label>
<input type="range" min="1" max="10" step="0.5" value="5.5" />
</fieldset>
<fieldset class="p-operational-complexity">
<span class="value"></span>
<label>What is the operational / business complexity of the feature?</label>
<input type="range" min="1" max="10" step="0.5" value="5.5" />
</fieldset>
<fieldset class="p-feature-value">
<span class="value"></span>
<label>How valuable is the feature to the target users, truly?</label>
<input type="range" min="1" max="10" step="0.5" value="5.5" />
</fieldset>
<fieldset class="p-negative-impact">
<span class="value"></span>
<label>How much potential technical risk can the feature bring to the rest of the product?</label>
<input type="range" min="1" max="10" step="0.5" value="5.5" />
</fieldset>
</form>
<div class="text-center">
<button id="btn-toggle-answer">Show answer</button>
</div>
<div class="answer hide">
<h1><span><span class="bool"></span> (<span class="percent"></span>)</span></h1>
<textarea class="summary" readonly></textarea>
<textarea hidden class="md-summary" readonly></textarea>
<button class="copy">Copy</button>
<button class="copy-md">Copy as Markdown</button>
</div>
</section>
<noscript><h1>This page requires Javascript to function.</h1></noscript>
<section>
<p>
Technical decisions are often about being as less-wrong as possible than being right.
Decision parameters themselves are dependent on even more parameters, such as the business environment, expertise, team and user culture,
cost ... There is no one-size-fits all framework for making technical decisions, especially when it comes
to adding a new feature to a product as they often (rightly) tend to be subjective bets than scientific conclusions.
</p>
<p>
This is one opinionated decision making system (a highly simplified version of it) that tries to bring in
elements of objectivity to making feature decisions in software products. This system has worked pretty well for me personally
and at work, <a href="https://zerodha.com">Zerodha</a>. So, it might work for others too. View this page's source for the logic and rationale.
Context:
<a href="https://zerodha.tech/blog/hello-world/">[1]</a>
<a href="https://zerodha.tech/blog/scaling-with-common-sense/">[2]</a>
<a href="https://zerodha.tech/blog/being-future-ready-with-common-sense/">[3]</a>
<a href="https://nadh.in/blog/fomo-yamo/">[4]</a>
</p>
</section>
<footer><a href="https://nadh.in">Kailash Nadh</a> — 2022 / <a href="https://github.com/knadh/dont.build">GitHub</a></footer>
</div>
<script>
(function() {
const params = [
{ weight: 0.10, value: 5.5, negative: false, name: 'p-users', },
{ weight: 0.20, value: 5.5, negative: false, name: 'p-user-value',},
{ weight: 0.20, value: 5.5, negative: false, name: 'p-feature-value',},
{ weight: 0.15, value: 5.5, negative: true, name: 'p-effort', },
{ weight: 0.20, value: 5.5, negative: true, name: 'p-negative-impact',},
{ weight: 0.15, value: 5.5, negative: true, name: 'p-operational-complexity',},
]
params.forEach((p, n) => {
let net = 0;
const inp = document.querySelector(`.${p.name} input`);
inp.value = p.value;
inp.oninput = (e) => {
const val = parseFloat(e.target.value, 64);
document.querySelector(`.${p.name} .value`).innerText = val;
p.value = val;
// Sum all weighted params.
const sum = params.reduce((prev, pm) => {
let v = pm.value;
if (pm.negative) {
v = 10 - v;
}
return prev + (v / 10 * pm.weight);
}, 0);
// Display the results.
const percent = `${(sum * 100).toFixed(0)}%`
let b = 'Yes';
if (sum < 0.5 && sum > 0.46) {
b = `No, but a close call.`;
} else if (sum < 0.48) {
b = `No.`;
} else if (sum > 0.5 && sum < 0.54) {
b = `Yes, but a close call.`;
} else if(sum === 0.5) {
b = '50-50. Go with the gut.';
}
document.querySelector(".answer .percent").innerText = percent;
document.querySelector(".answer .bool").innerText = b;
// Print the summary in the textbox.
let summary = '';
let mdSummary = '||Question|Answer|\n|---|---|---|\n';
document.querySelectorAll(".form fieldset").forEach((f, n) => {
summary += `${n + 1}. ${f.querySelector("label").innerText} = ${f.querySelector("input").value}\n`;
mdSummary += `|${n + 1}|${f.querySelector("label").innerText}|${f.querySelector("input").value}|\n`;
});
summary += `\n=> ${b} (${percent})`;
mdSummary += `||Result:|${b} (${percent})|`;
document.querySelector('.summary').value = summary;
document.querySelector('.md-summary').value = mdSummary;
};
inp.dispatchEvent(new Event('input'));
});
})();
(function () {
const ans = document.querySelector('.answer');
const btn = document.querySelector('#btn-toggle-answer');
btn.addEventListener('click', function() {
if(ans.style.display === 'block') {
ans.style.display = 'none';
btn.innerText = "Show answer";
} else {
ans.style.display = 'block';
btn.innerText = "Hide answer";
}
});
})();
(function () {
const copyBtn = document.querySelector('.copy');
const copyMdBtn = document.querySelector('.copy-md');
var copyToClipboard = function(val) {
var tempTxt=document.createElement("textarea");
tempTxt.value=val;
document.body.appendChild(tempTxt);
tempTxt.select();
document.execCommand("copy");
document.body.removeChild(tempTxt);
let notice = document.createElement("span");
notice.className = "notice visible";
notice.innerHTML = "Copied to clipboard.";
document.body.appendChild(notice);
setTimeout(() => {
document.body.removeChild(notice);
}, 3000);
}
copyBtn.addEventListener('click', function() {
copyToClipboard(document.querySelector('.summary').value);
});
copyMdBtn.addEventListener('click', function() {
copyToClipboard(document.querySelector('.md-summary').value);
});
})();
</script>
</body>
</html>