Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2889743
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
24 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/docs/book/flavor.book b/src/docs/book/flavor.book
index 4404e94bf7..c9a80fae92 100644
--- a/src/docs/book/flavor.book
+++ b/src/docs/book/flavor.book
@@ -1,39 +1,39 @@
{
"name": "flavor",
"title": "Phorge Flavor Text",
"short": "Flavor Text",
"preface": "A collection of short articles which pertain to software development in general, not necessarily to Phorge specifically.",
"root": "../../../",
"uri.source":
"https://we.phorge.it/diffusion/P/browse/master/%f$%l",
"rules": {
"(\\.diviner$)": "DivinerArticleAtomizer"
},
"exclude": [
"(^externals/)",
"(^resources/)",
"(^scripts/)",
"(^src/docs/contributor/)",
"(^src/docs/tech/)",
"(^src/docs/user/)",
"(^support/)",
"(^webroot/rsrc/externals/)"
],
"groups": {
"javascript": {
- "name": "Javascript"
+ "name": "JavaScript"
},
"lore": {
"name": "Phorge Lore"
},
"php": {
"name": "PHP"
},
"review": {
"name": "Revision Control and Code Review"
},
"sundry": {
"name": "Sundries"
}
}
}
diff --git a/src/docs/flavor/javascript_object_array.diviner b/src/docs/flavor/javascript_object_array.diviner
index cdba029e9a..113dbb1000 100644
--- a/src/docs/flavor/javascript_object_array.diviner
+++ b/src/docs/flavor/javascript_object_array.diviner
@@ -1,152 +1,164 @@
-@title Javascript Object and Array
+@title JavaScript Object and Array
@group javascript
-This document describes the behaviors of Object and Array in Javascript, and
+This document describes the behaviors of Object and Array in JavaScript, and
a specific approach to their use which produces basically reasonable language
behavior.
= Primitives =
-Javascript has two native datatype primitives, Object and Array. Both are
+JavaScript has two native datatype primitives, Object and Array. Both are
classes, so you can use `new` to instantiate new objects and arrays:
COUNTEREXAMPLE
var a = new Array(); // Not preferred.
var o = new Object();
However, **you should prefer the shorthand notation** because it's more concise:
lang=js
var a = []; // Preferred.
var o = {};
(A possible exception to this rule is if you want to use the allocation behavior
of the Array constructor, but you almost certainly don't.)
The language relationship between Object and Array is somewhat tricky. Object
and Array are both classes, but "object" is also a primitive type. Object is
//also// the base class of all classes.
lang=js
typeof Object; // "function"
typeof Array; // "function"
typeof {}; // "object"
typeof []; // "object"
var a = [], o = {};
o instanceof Object; // true
o instanceof Array; // false
a instanceof Object; // true
a instanceof Array; // true
= Objects are Maps, Arrays are Lists =
PHP has a single `array` datatype which behaves like as both map and a list,
-and a common mistake is to treat Javascript arrays (or objects) in the same way.
+and a common mistake is to treat JavaScript arrays (or objects) in the same way.
**Don't do this.** It sort of works until it doesn't. Instead, learn how
-Javascript's native datatypes work and use them properly.
+JavaScript's native datatypes work and use them properly.
-In Javascript, you should think of Objects as maps ("dictionaries") and Arrays
+In JavaScript, you should think of Objects as maps ("dictionaries") and Arrays
as lists ("vectors").
You store keys-value pairs in a map, and store ordered values in a list. So,
store key-value pairs in Objects.
var o = { // Good, an object is a map.
name: 'Hubert',
species: 'zebra'
};
- console.log(o.name);
+ o.paws = 4;
+
+ o['numberOfEars'] = 2;
+
+ console.log(o.name);
+ console.log(o.paws);
+ console.log(o.numberOfEars);
...and store ordered values in Arrays.
var a = [1, 2, 3]; // Good, an array is a list.
a.push(4);
Don't store key-value pairs in Arrays and don't expect Objects to be ordered.
COUNTEREXAMPLE
var a = [];
a['name'] = 'Hubert'; // No! Don't do this!
-This technically works because Arrays are Objects and you think everything is
-fine and dandy, but it won't do what you want and will burn you.
+Technically, both work because Arrays //are// Objects and you think everything
+is fine and dandy, but it won't do what you want and will burn you. For example,
+using `.length` will play tricks on you.
+
+In short, trust me:
+
+* use `[]` only to create a stack of consecutive elements numerically indexed
+* use `{}` to create associative maps ("associative arrays")
= Iterating over Maps and Lists =
Iterate over a map like this:
lang=js
for (var k in object) {
f(object[k]);
}
NOTE: There's some hasOwnProperty nonsense being omitted here, see below.
Iterate over a list like this:
lang=js
for (var ii = 0; ii < list.length; ii++) {
f(list[ii]);
}
NOTE: There's some sparse array nonsense being omitted here, see below.
If you try to use `for (var k in ...)` syntax to iterate over an Array, you'll
pick up a whole pile of keys you didn't intend to and it won't work. If you try
to use `for (var ii = 0; ...)` syntax to iterate over an Object, it won't work
at all.
If you consistently treat Arrays as lists and Objects as maps and use the
corresponding iterators, everything will pretty much always work in a reasonable
way.
= hasOwnProperty() =
An issue with this model is that if you write stuff to Object.prototype, it will
show up every time you use enumeration `for`:
COUNTEREXAMPLE
var o = {};
Object.prototype.duck = "quack";
for (var k in o) {
console.log(o[k]); // Logs "quack"
}
There are two ways to avoid this:
- test that `k` exists on `o` by calling `o.hasOwnProperty(k)` in every
single loop everywhere in your program and only use libraries which also do
this and never forget to do it ever; or
- don't write to Object.prototype.
Of these, the first option is terrible garbage. Go with the second option.
= Sparse Arrays =
Another wrench in this mess is that Arrays aren't precisely like lists, because
they do have indexes and may be sparse:
var a = [];
a[2] = 1;
console.log(a); // [undefined, undefined, 1]
The correct way to deal with this is:
for (var ii = 0; ii < list.length; ii++) {
if (list[ii] == undefined) {
continue;
}
f(list[ii]);
- }
+ }
Avoid sparse arrays if possible.
= Ordered Maps =
If you need an ordered map, you need to have a map for key-value associations
and a list for key order. Don't try to build an ordered map using one Object or
one Array. This generally applies for other complicated datatypes, as well; you
need to build them out of more than one primitive.
diff --git a/src/docs/flavor/javascript_pitfalls.diviner b/src/docs/flavor/javascript_pitfalls.diviner
index 9276ae4712..fbc2eae05c 100644
--- a/src/docs/flavor/javascript_pitfalls.diviner
+++ b/src/docs/flavor/javascript_pitfalls.diviner
@@ -1,87 +1,87 @@
-@title Javascript Pitfalls
+@title JavaScript Pitfalls
@group javascript
-This document discusses pitfalls and flaws in the Javascript language, and how
+This document discusses pitfalls and flaws in the JavaScript language, and how
to avoid, work around, or at least understand them.
= Implicit Semicolons =
-Javascript tries to insert semicolons if you forgot them. This is a pretty
+JavaScript tries to insert semicolons if you forgot them. This is a pretty
horrible idea. Notably, it can mask syntax errors by transforming subexpressions
on their own lines into statements with no effect:
lang=js
string = "Here is a fairly long string that does not fit on one "
"line. Note that I forgot the string concatenation operators "
"so this will compile and execute with the wrong behavior. ";
Here's what ECMA262 says about this:
When, as the program is parsed ..., a token ... is encountered that is not
allowed by any production of the grammar, then a semicolon is automatically
inserted before the offending token if one or more of the following conditions
is true: ...
To protect yourself against this "feature", don't use it. Always explicitly
insert semicolons after each statement. You should also prefer to break lines in
places where insertion of a semicolon would not make the unparseable parseable,
usually after operators.
= `with` is Bad News =
`with` is a pretty bad feature, for this reason among others:
with (object) {
property = 3; // Might be on object, might be on window: who knows.
}
Avoid `with`.
= `arguments` is not an Array =
You can convert `arguments` to an array using JX.$A() or similar. Note that
you can pass `arguments` to Function.prototype.apply() without converting it.
= Object, Array, and iteration are needlessly hard =
There is essentially only one reasonable, consistent way to use these primitives
but it is not obvious. Navigate these troubled waters with
-@{article:Javascript Object and Array}.
+@{article:JavaScript Object and Array}.
= typeof null == "object" =
-This statement is true in Javascript:
+This statement is true in JavaScript:
typeof null == 'object'
This is pretty much a bug in the language that can never be fixed now.
= Number, String, and Boolean objects =
-Like Java, Javascript has primitive versions of number, string, and boolean,
+Like Java, JavaScript has primitive versions of number, string, and boolean,
and object versions. In Java, there's some argument for this distinction. In
-Javascript, it's pretty much completely worthless and the behavior of these
+JavaScript, it's pretty much completely worthless and the behavior of these
objects is wrong. String and Boolean in particular are essentially unusable:
lang=js
"pancake" == "pancake"; // true
new String("pancake") == new String("pancake"); // false
var b = new Boolean(false);
b; // Shows 'false' in console.
!b; // ALSO shows 'false' in console.
!b == b; // So this is true!
!!b == !b // Negate both sides and it's false! FUCK!
if (b) {
// Better fucking believe this will get executed.
}
There is no advantage to using the object forms (the primitive forms behave like
objects and can have methods and properties, and inherit from Array.prototype,
Number.prototype, etc.) and their logical behavior is at best absurd and at
worst strictly wrong.
**Never use** `new Number()`, `new String()` or `new Boolean()` unless
-your Javascript is God Tier and you are absolutely sure you know what you are
+your JavaScript is God Tier and you are absolutely sure you know what you are
doing.
diff --git a/src/docs/flavor/php_pitfalls.diviner b/src/docs/flavor/php_pitfalls.diviner
index 3f4be45dd7..e15ca9e2bb 100644
--- a/src/docs/flavor/php_pitfalls.diviner
+++ b/src/docs/flavor/php_pitfalls.diviner
@@ -1,329 +1,329 @@
@title PHP Pitfalls
@group php
This document discusses difficult traps and pitfalls in PHP, and how to avoid,
work around, or at least understand them.
= `array_merge()` in Incredibly Slow When Merging A List of Arrays =
If you merge a list of arrays like this:
COUNTEREXAMPLE, lang=php
$result = array();
foreach ($list_of_lists as $one_list) {
$result = array_merge($result, $one_list);
}
...your program now has a huge runtime because it generates a large number of
intermediate arrays and copies every element it has previously seen each time
you iterate.
In a libphutil environment, you can use @{function@arcanist:array_mergev}
instead.
= `var_export()` Hates Baby Animals =
If you try to `var_export()` an object that contains recursive references, your
program will terminate. You have no chance to intercept or react to this or
otherwise stop it from happening. Avoid `var_export()` unless you are certain
you have only simple data. You can use `print_r()` or `var_dump()` to display
complex variables safely.
= `isset()`, `empty()` and Truthiness =
A value is "truthy" if it evaluates to true in an `if` clause:
lang=php
$value = something();
if ($value) {
// Value is truthy.
}
If a value is not truthy, it is "falsey". These values are falsey in PHP:
null // null
0 // integer
0.0 // float
"0" // string
"" // empty string
false // boolean
array() // empty array
Disregarding some bizarre edge cases, all other values are truthy. Note that
because "0" is falsey, this sort of thing (intended to prevent users from making
empty comments) is wrong in PHP:
COUNTEREXAMPLE
if ($comment_text) {
make_comment($comment_text);
}
This is wrong because it prevents users from making the comment "0". //THIS
COMMENT IS TOTALLY AWESOME AND I MAKE IT ALL THE TIME SO YOU HAD BETTER NOT
BREAK IT!!!// A better test is probably `strlen()`.
In addition to truth tests with `if`, PHP has two special truthiness operators
which look like functions but aren't: `empty()` and `isset()`. These operators
help deal with undeclared variables.
In PHP, there are two major cases where you get undeclared variables -- either
you directly use a variable without declaring it:
COUNTEREXAMPLE, lang=php
function f() {
if ($not_declared) {
// ...
}
}
...or you index into an array with an index which may not exist:
COUNTEREXAMPLE
function f(array $mystery) {
if ($mystery['stuff']) {
// ...
}
}
When you do either of these, PHP issues a warning. Avoid these warnings by
using `empty()` and `isset()` to do tests that are safe to apply to undeclared
variables.
`empty()` evaluates truthiness exactly opposite of `if()`. `isset()` returns
`true` for everything except `null`. This is the truth table:
| Value | `if()` | `empty()` | `isset()` |
|-------|--------|-----------|-----------|
| `null` | `false` | `true` | `false` |
| `0` | `false` | `true` | `true` |
| `0.0` | `false` | `true` | `true` |
| `"0"` | `false` | `true` | `true` |
| `""` | `false` | `true` | `true` |
| `false` | `false` | `true` | `true` |
| `array()` | `false` | `true` | `true` |
| Everything else | `true` | `false` | `true` |
The value of these operators is that they accept undeclared variables and do
not issue a warning. Specifically, if you try to do this you get a warning:
```lang=php, COUNTEREXAMPLE
if ($not_previously_declared) { // PHP Notice: Undefined variable!
// ...
}
```
But these are fine:
```lang=php
if (empty($not_previously_declared)) { // No notice, returns true.
// ...
}
if (isset($not_previously_declared)) { // No notice, returns false.
// ...
}
```
So, `isset()` really means
`is_declared_and_is_set_to_something_other_than_null()`. `empty()` really means
`is_falsey_or_is_not_declared()`. Thus:
- If a variable is known to exist, test falsiness with `if (!$v)`, not
`empty()`. In particular, test for empty arrays with `if (!$array)`. There
is no reason to ever use `empty()` on a declared variable.
- When you use `isset()` on an array key, like `isset($array['key'])`, it
will evaluate to "false" if the key exists but has the value `null`! Test
for index existence with `array_key_exists()`.
Put another way, use `isset()` if you want to type `if ($value !== null)` but
are testing something that may not be declared. Use `empty()` if you want to
type `if (!$value)` but you are testing something that may not be declared.
= usort(), uksort(), and uasort() are Slow =
This family of functions is often extremely slow for large datasets. You should
avoid them if at all possible. Instead, build an array which contains surrogate
keys that are naturally sortable with a function that uses native comparison
(e.g., `sort()`, `asort()`, `ksort()`, or `natcasesort()`). Sort this array
instead, and use it to reorder the original array.
In a libphutil environment, you can often do this easily with
@{function@arcanist:isort} or @{function@arcanist:msort}.
= `array_intersect()` and `array_diff()` are Also Slow =
These functions are much slower for even moderately large inputs than
`array_intersect_key()` and `array_diff_key()`, because they can not make the
assumption that their inputs are unique scalars as the `key` varieties can.
Strongly prefer the `key` varieties.
= `array_uintersect()` and `array_udiff()` are Definitely Slow Too =
These functions have the problems of both the `usort()` family and the
`array_diff()` family. Avoid them.
= `foreach()` Does Not Create Scope =
Variables survive outside of the scope of `foreach()`. More problematically,
references survive outside of the scope of `foreach()`. This code mutates
`$array` because the reference leaks from the first loop to the second:
```lang=php, COUNTEREXAMPLE
$array = range(1, 3);
echo implode(',', $array); // Outputs '1,2,3'
foreach ($array as &$value) {}
echo implode(',', $array); // Outputs '1,2,3'
foreach ($array as $value) {}
echo implode(',', $array); // Outputs '1,2,2'
```
The easiest way to avoid this is to avoid using foreach-by-reference. If you do
use it, unset the reference after the loop:
```lang=php
foreach ($array as &$value) {
// ...
}
unset($value);
```
= `unserialize()` is Incredibly Slow on Large Datasets =
The performance of `unserialize()` is nonlinear in the number of zvals you
unserialize, roughly `O(N^2)`.
| zvals | Approximate time |
|-------|------------------|
| 10000 |5ms |
| 100000 | 85ms |
| 1000000 | 8,000ms |
| 10000000 | 72 billion years |
= `call_user_func()` Breaks References =
If you use `call_use_func()` to invoke a function which takes parameters by
reference, the variables you pass in will have their references broken and will
emerge unmodified. That is, if you have a function that takes references:
```lang=php
function add_one(&$v) {
$v++;
}
```
...and you call it with `call_user_func()`:
```lang=php, COUNTEREXAMPLE
$x = 41;
call_user_func('add_one', $x);
```
...`$x` will not be modified. The solution is to use `call_user_func_array()`
and wrap the reference in an array:
```lang=php
$x = 41;
call_user_func_array(
'add_one',
array(&$x)); // Note '&$x'!
```
This will work as expected.
= You Can't Throw From `__toString()` =
If you throw from `__toString()`, your program will terminate uselessly and you
won't get the exception.
= An Object Can Have Any Scalar as a Property =
Object properties are not limited to legal variable names:
```lang=php
$property = '!@#$%^&*()';
$obj->$property = 'zebra';
echo $obj->$property; // Outputs 'zebra'.
```
So, don't make assumptions about property names.
= There is an `(object)` Cast =
You can cast a dictionary into an object.
```lang=php
$obj = (object)array('flavor' => 'coconut');
echo $obj->flavor; // Outputs 'coconut'.
echo get_class($obj); // Outputs 'stdClass'.
```
-This is occasionally useful, mostly to force an object to become a Javascript
+This is occasionally useful, mostly to force an object to become a JavaScript
dictionary (vs a list) when passed to `json_encode()`.
= Invoking `new` With an Argument Vector is Really Hard =
If you have some `$class_name` and some `$argv` of constructor arguments
and you want to do this:
```lang=php
new $class_name($argv[0], $argv[1], ...);
```
...you'll probably invent a very interesting, very novel solution that is very
wrong. In a libphutil environment, solve this problem with
@{function@arcanist:newv}. Elsewhere, copy `newv()`'s implementation.
= Equality is not Transitive =
This isn't terribly surprising since equality isn't transitive in a lot of
languages, but the `==` operator is not transitive:
```lang=php
$a = ''; $b = 0; $c = '0a';
$a == $b; // true
$b == $c; // true
$c == $a; // false!
```
When either operand is an integer, the other operand is cast to an integer
before comparison. Avoid this and similar pitfalls by using the `===` operator,
which is transitive.
= All 676 Letters in the Alphabet =
This doesn't do what you'd expect it to do in C:
```lang=php
for ($c = 'a'; $c <= 'z'; $c++) {
// ...
}
```
This is because the successor to `z` is `aa`, which is "less than" `z`.
The loop will run for ~700 iterations until it reaches `zz` and terminates.
That is, `$c` will take on these values:
```
a
b
...
y
z
aa // loop continues because 'aa' <= 'z'
ab
...
mf
mg
...
zw
zx
zy
zz // loop now terminates because 'zz' > 'z'
```
Instead, use this loop:
```lang=php
foreach (range('a', 'z') as $c) {
// ...
}
```
diff --git a/src/docs/flavor/project_history.diviner b/src/docs/flavor/project_history.diviner
index 1159304cda..6b51e203ed 100644
--- a/src/docs/flavor/project_history.diviner
+++ b/src/docs/flavor/project_history.diviner
@@ -1,63 +1,63 @@
@title Phorge Project History
@group lore
A riveting tale of adventure.
= In The Beginning =
Evan Priestley wrote the original version of Differential in one night at a
Facebook Hackathon in April or May 2007, along with Luke Shepard. He joined the
company in April and code review was already an established and mostly-mandatory
part of the culture, but it happened over email and was inefficient and hard to
keep track of. Evan remembers feeling like he was spending a lot of time waiting
for code review to happen, which was a major motivator for building the tool.
The original name of the tool was "Diffcamp". Some time earlier there had been
an attempt to create a project management tool that was a sort of hybrid between
Trac and Basecamp called "Traccamp". Since they were writing the code review tool
at the height of the brief popularity Traccamp enjoyed, Evan and Luke integrated
and called the new tool Diffcamp even though it had no relation to Basecamp.
Traccamp fell by the wayside shortly thereafter and was eventually removed.
However, Diffcamp didn't share its fate. Evan and Luke spent some more time
working on it and got good enough to win hearts and minds over emailing diffs
around and was soon the de facto method of code review at Facebook.
= The Long Bloat =
For the next two and a half years, Diffcamp grew mostly organically and gained a
number of features like inline commenting, CLI support and git support (Facebook
was 100% SVN in early 2007 but 90%+ of Engineers worked primarily in git with
SVN bridging by 2010). As these patches were contributed pretty much randomly,
it also gained a lot of performance problems, usability issues, and bugs.
Through 2007 and 2008 Evan worked mostly on frontend and support infrastructure;
among other things, he wrote a static resource management system called Haste.
In 2009 Evan worked on the Facebook Lite site, where he built the Javelin
-Javascript library and an MVC-flavored framework called Alite.
+JavaScript library and an MVC-flavored framework called Alite.
But by early 2010, Diffcamp was in pretty bad shape. Two years of having random
features grafted onto it without real direction had left it slow and difficult
to use. Internal feedback on the tool was pretty negative, with a lot of
complaints about performance and stability. The internal XTools team had made
inroads at fixing these problems in late 2009, but they were stretched thin and
the tool had become a sprawling landscape of architectural and implementation
problems.
= Differential =
Evan joined the new Dev Tools team around February 2010 and took over Diffcamp.
He renamed it to Differential, moved it to a new Alite-based infrastructure with
Javelin, and started making it somewhat less terrible. He eventually wrote
Diffusion and built Herald to replace a very difficult-to-use predecessor. These
tools were less negatively received than the older versions. By December 2010,
Evan started open sourcing them; Haste became //Celerity// and Alite became
//Aphront//. He wrote Maniphest to track open issues with the project in January
or February, left Facebook in April, and shortly after, open sourced
Phabricator.
= Phork =
In 2021, Evan announced that Phabricator was no longer maintained. A group of
open-source contributors came together and forked it. This new
group called renamed the project "Phorge" and continues to maintain this
beloved and well-used project.
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 12:35 (3 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1124625
Default Alt Text
(24 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment