Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre programming (publicaciones antiguas, página 69)

En 128 líneas de código entra exactamente ESTE browser.

Por su­pues­to, po­dría ha­cer má­s, pe­ro has­ta yo ten­go mis stan­dar­d­s!

  • No usar ;

  • No usar if whate­ve­r: f()

Sal­vo eso, hi­ce al­gu­nos tru­cos su­cio­s, pe­ro, en es­te mo­men­to, es un bro­w­ser bas­tan­te com­ple­to en 127 lí­neas de có­di­go se­gún sloc­coun­t, así que ya ju­gué su­fi­cien­te y ma­ña­na ten­go tra­ba­jo que ha­ce­r.

Pe­ro an­tes, con­si­de­re­mos co­mo se im­ple­men­ta­ron al­gu­nos fea­tu­res (voy a cor­tar las lí­neas pa­ra que la pá­gi­na que­de ra­zo­na­ble­men­te an­gos­ta), y vea­mos tam­bién las ver­sio­nes "nor­ma­le­s" de lo mis­mo. La ver­sión "nor­ma­l" no es­tá pro­ba­da, avi­sen si es­tá ro­ta ;-)

Es­to noes al­go que de­ba apren­der­se. De he­cho es ca­si un tra­ta­do en co­mo no ha­cer las co­sas. Es el có­di­go me­nos pi­tó­ni­co y me­nos cla­ro que vas a ver es­ta se­ma­na.

Es cor­to, es ex­pre­si­vo, pe­ro es feo feo.

Voy a co­men­tar so­bre es­ta ver­sión.

Soporte deProxy

Un bro­w­ser no es gran co­sa si no se pue­de usar con pro­x­y. Por suer­te el sta­ck de red de Qt tie­ne buen so­por­te de pro­x­y. El chis­te es con­fi­gu­rar­lo.

De Vicenzo soporta proxies HTTP y SOCKS parseando la variable de entorno http_proxy y seteando el proxy a nivel aplicación en Qt:

 proxy_url = QtCore.QUrl(os.environ.get('http_proxy', ''))
 QtNetwork.QNetworkProxy.setApplicationProxy(QtNetwork.QNetworkProxy(\
 QtNetwork.QNetworkProxy.HttpProxy if unicode(proxy_url.scheme()).startswith('http')\
 else QtNetwork.QNetworkProxy.Socks5Proxy, proxy_url.host(),\
 proxy_url.port(), proxy_url.userName(), proxy_url.password())) if\
'http_proxy' in os.environ else None

Co­mo es la ver­sión nor­mal de esa co­sa?

if 'http_proxy' in os.environ:
    proxy_url = QtCore.QUrl(os.environ['http_proxy'])
    if unicode(proxy_url.scheme()).starstswith('http'):
        protocol = QtNetwork.QNetworkProxy.HttpProxy
    else:
        protocol = QtNetwork.QNetworkProxy.Socks5Proxy
    QtNetwork.QNetworkProxy.setApplicationProxy(
        QtNetwork.QNetworkProxy(
            protocol,
            proxy_url.host(),
            proxy_url.port(),
            proxy_url.userName(),
            proxy_url.password()))

Los abu­sos prin­ci­pa­les contra py­thon son el uso del ope­ra­dor ter­na­rio pa­ra ha­cer un if de una lí­nea (y ani­dar­lo) y el lar­go de lí­nea.

Cookies Persistentes

Es­to es ne­ce­sa­rio por­que que­rés per­ma­ne­cer lo­guea­do en los si­tios de una se­sión a otra. Pa­ra es­to, pri­me­ro tu­ve que ha­cer un pe­que­ño me­ca­nis­mo de per­sis­ten­cia, y guar­da­r/­leer los cookies de ahí.

Acá está como hice la persistencia (settings is una instancia de QSettings global):

def put(self, key, value):
    "Persist an object somewhere under a given key"
    settings.setValue(key, json.dumps(value))
    settings.sync()

def get(self, key, default=None):
    "Get the object stored under 'key' in persistent storage, or the default value"
    v = settings.value(key)
    return json.loads(unicode(v.toString())) if v.isValid() else default

No es có­di­go muy ra­ro, sal­vo por usar el ope­ra­dor ter­na­rio al fi­na­l. El uso de json me ase­gu­ra que mien­tras me­ta co­sas ra­zo­na­ble­s, voy a ob­te­ner lo mis­mo de vuel­ta, con el mis­mo ti­po, sin ne­ce­si­dad de con­ver­tir­lo o lla­mar mé­to­dos es­pe­cia­le­s.

¿Entonces, como guardo/leo los cookies? Primero se necesita acceder el "cookie jar". No encontré si hay uno global o por view, así que creé un QNetworkCookieJar en la línea 24 y la asigno a cada página en la línea 107.

# Save the cookies, in the window's closeEvent
self.put("cookiejar", [str(c.toRawForm()) for c in self.cookies.allCookies()])

# Restore the cookies, in the window's __init__
self.cookies.setAllCookies([QtNetwork.QNetworkCookie.parseCookies(c)[0]\
for c in self.get("cookiejar", [])])

Confieso mi crimen de usar comprensiones de listas cuando la herramienta correcta era un for.

Uso el mis­mo tru­co al res­tau­rar los ta­bs abier­to­s, con el mo­co agre­ga­do de usar una com­pren­sión de lis­ta y des­car­tar el re­sul­ta­do:

# get("tabs") is a list of URLs
[self.addTab(QtCore.QUrl(u)) for u in self.get("tabs", [])]

Propiedades y Señales al crear un objeto

Es­te fea­tu­re es­tá en ver­sio­nes re­cien­tes de Py­Q­t: si pa­sás nom­bres de pro­pie­da­des co­mo ar­gu­men­tos con nom­bre, se les asig­na el va­lo­r. Si pa­sás una se­ñal co­mo ar­gu­men­to con nom­bre, se co­nec­tan al va­lo­r.

Es un fea­tu­re ex­ce­len­te, que te ayu­da a crear có­di­go cla­ro, lo­cal y con­ci­so, y me en­can­ta te­ner­lo. Pe­ro si te que­rés ir a la ban­qui­na, es man­da­da a ha­ce­r.

Es­to es­tá por to­dos la­dos en De Vi­cen­zo, és­te es só­lo un ejem­plo (sí, es una so­la lí­nea):

QtWebKit.QWebView.__init__(self, loadProgress=lambda v:\
(self.pbar.show(), self.pbar.setValue(v)) if self.amCurrent() else\
None, loadFinished=self.pbar.hide, loadStarted=lambda:\
self.pbar.show() if self.amCurrent() else None, titleChanged=lambda\
t: container.tabs.setTabText(container.tabs.indexOf(self), t) or\
(container.setWindowTitle(t) if self.amCurrent() else None))

Por adon­de em­pie­zo­...

Hay expresiones lambda usadas para definir los callbacks en el lugar en vez de conectarse con una función o método "de verdad".

Hya lamb­das con el ope­ra­dor ter­na­rio:

loadStarted=lambda:\
    self.pbar.show() if self.amCurrent() else None

Hay lambdas que usan or o una tupla para engañar al intérprete y que haga más de una cosa en un solo lambda!

loadProgress=lambda v:\
(self.pbar.show(), self.pbar.setValue(v)) if self.amCurrent() else\
None

No voy ni a in­ten­tar des­en­re­dar es­to con fi­nes edu­ca­ti­vo­s, pe­ro di­ga­mos que esa lí­nea con­tie­ne co­sas que de­be­rían ser 3 mé­to­dos se­pa­ra­do­s, y de­be­ría es­tar re­par­ti­da en 6 lí­neas o ma­s.

Download Manager

Lla­mar­lo un ma­na­ger es exa­ge­rar por­que no se pue­de pa­rar una des­car­ga des­pués que em­pie­za, pe­ro bue­no, te de­ja ba­jar co­sas y se­guir bro­w­sean­do, y te da un re­por­te de pro­gre­so!

Primero, en la línea 16 creé un diccionario bars para llevar registro de los downloads.

Des­pué­s, te­nía que de­le­gar el con­te­ni­do no so­por­ta­do al mé­to­do in­di­ca­do, y eso se ha­ce en las lí­neas 108 and 109

Básicamente, con eso cada vez que hacés click en algo que WebKit no puede manejar, se llama al método fetch con el pedido de red como argumento.

def fetch(self, reply):
    destination = QtGui.QFileDialog.getSaveFileName(self, \
        "Save File", os.path.expanduser(os.path.join('~',\
            unicode(reply.url().path()).split('/')[-1])))
    if destination:
        bar = QtGui.QProgressBar(format='%p% - ' +
            os.path.basename(unicode(destination)))
        self.statusBar().addPermanentWidget(bar)
        reply.downloadProgress.connect(self.progress)
        reply.finished.connect(self.finished)
        self.bars[unicode(reply.url().toString())] = [bar, reply,\
            unicode(destination)]

No hay mu­cho golf acá sal­vo las lí­neas lar­ga­s, pe­ro una vez que me­tés en­ters es la ma­ne­ra ob­via de ha­cer­lo:

  • Pe­­dí un no­m­­bre de ar­­chi­­vo

  • Creás un pro­­­gress­­ba­­r, lo po­­­nés en el sta­­tus­­ba­­r, y lo co­­­ne­c­­tas a las se­­ña­­les de pro­­­gre­­so de la des­­ca­r­­ga.

Entonces, por supuesto, está el slot progress que actualiza la barra:

progress = lambda self, received, total:\
    self.bars[unicode(self.sender().url().toString())][0]\
    .setValue(100. * received / total)

Sí, de­fi­ní un mé­to­do co­mo lamb­da pa­ra aho­rrar una lí­nea. [fa­ce­pal­m]

Y elslot finished para cuando termina el download:

def finished(self):
    reply = self.sender()
    url = unicode(reply.url().toString())
    bar, _, fname = self.bars[url]
    redirURL = unicode(reply.attribute(QtNetwork.QNetworkRequest.\
        RedirectionTargetAttribute).toString())
    del self.bars[url]
    bar.deleteLater()
    if redirURL and redirURL != url:
        return self.fetch(redirURL, fname)
    with open(fname, 'wb') as f:
        f.write(str(reply.readAll()))

has­ta so­por­ta re­di­rec­cio­nes co­rrec­ta­men­te! Más allá d eso, na­da más es­con­de la ba­rra, guar­da los da­to­s, fin del cuen­ti­to. La lí­nea lar­ga ni si­quie­ra es mi cul­pa!

Hay un pro­ble­ma en que el ar­chi­vo en­te­ro se man­tie­ne en me­mo­ria has­ta el fin de la des­car­ga. Si te ba­jás un DV­D, te va a do­le­r.

Usar el with ahorra una línea y no pierde un file handle, comparado con las alternativas.

Impresión

De nue­vo Qt me sal­va las pa­pa­s, por­que ha­cer es­to a ma­no de­be ser di­fí­ci­l. Sin em­bar­go, re­sul­ta que el so­por­te de im­pre­sió­n... es­tá he­cho. Qt, es­pe­cial­men­te usa­do vía Py­Qt es tan com­ple­to!

self.previewer = QtGui.QPrintPreviewDialog(\
    paintRequested=self.print_)
self.do_print = QtGui.QShortcut("Ctrl+p",\
    self, activated=self.previewer.exec_)

No ne­ce­si­té na­da de gol­f. Eso es exac­ta­men­te el có­di­go que se ne­ce­si­ta, y es la ma­ne­ra re­co­men­da­da de en­gan­char "C­tr­l+­p" con la im­pre­sión de la pá­gi­na.

Otros Trucos

No hay otros tru­co­s. To­do lo que que­da es crear wi­dge­ts, co­nec­tar unas co­sas con otra­s, y dis­fru­tar la in­creí­ble ex­pe­rien­ce de pro­gra­mar Py­Q­t, don­de po­dés es­cri­bir un web bro­w­ser en­te­ro (s­al­vo el mo­to­r) en 127 lí­neas de có­di­go.

De Vicenzo: un mini browser más copado

Si no que­rés leer eso de nue­vo, la idea es ver cuán­to có­di­go fal­ta pa­ra con­ver­tir el mo­tor We­bKit de Qt en un bro­w­ser "en se­rio­".

Pa­ra ello, me pu­se una me­ta com­ple­ta­men­te ar­bi­tra­ria de 128 lí­neas de có­di­go. En es­te mo­men­to lo de­cla­ro fea­tu­re-­com­ple­te (pe­ro bu­gg­y).

Los nue­vos fea­tu­res so­n:

  • Ta­­bbed bro­­w­­sing (se pue­­de agre­­ga­­r/s­a­­car ta­bs)

  • Book­­ma­­rks (se pue­­den agre­­ga­­r/s­a­­car y ele­­gir de una lis­­ta)

Es­to es lo que ya fun­cio­na­ba:

  • Zoom in (C­­tr­­l++)

  • Zoom out (C­­tr­­l+-)

  • Re­set Zoom (C­­tr­­l+=)

  • Bus­­car (C­­tr­­l+­­F)

  • Es­­co­n­­der bús­­que­­da (Es­­c)

  • Bo­­­to­­­nes de atrá­s/a­­de­­lan­­te y re­­ca­r­­gar

  • En­­tra­­da de URL que coi­n­­ci­­de con la pá­­gi­­na + au­­to­­­co­m­­ple­­ta­­do des­­de la his­­to­­­ria + arre­­gla la URL pues­­ta a ma­no (a­­gre­­ga http://, esas co­­sas)

  • Plu­­gins (i­n­­cluí­­do fla­s­h, que hay que ba­­jar apa­r­­te ;-)

  • El tí­­tu­­lo de la ven­­ta­­na mues­­tra el tí­­tu­­lo de la pá­­gi­­na (sin pro­­­pa­­gan­­da del bro­­w­se­­r)

  • Ba­­rra de pro­­­gre­­so pa­­ra la ca­r­­ga de la pá­­gi­­na

  • Ba­­rra de es­­ta­­do que mues­­tra el des­­tino de los li­nks cuan­­do pa­sas el mou­­se

  • To­­­ma una URL en la lí­­nea de co­­­man­­do (o abre http://­­p­­y­­tho­­n.org

  • Mu­l­­ti­­pla­­ta­­fo­r­­ma (fun­­cio­­­na do­n­­de fun­­cio­­­na QtWe­­bKi­­t)

Y cuan­to có­di­go es eso? 87 LI­NEAS.

O si pre­fe­rís la ver­sión que cum­ple con la PE­P8: 115 LI­NEAS.

Me ata­jo an­tes que al­guien lo di­ga: sí, el mo­tor de ren­de­ring y el toolkit son enor­mes. Lo que es­cri­bí es el "ch­ro­me" al­re­de­dor de eso, igual que ha­cen Aro­ra, Rekon­q, Ga­leo­n, Epi­phan­y, y mu­chos otros bro­w­ser­s.

Es un ch­ro­me sim­ple y mi­ni­ma­lis­ta, pe­ro fun­cio­na bas­tan­te bien, creo yo.

Aquí es­tá el de­mo (bu­gg­y):

Mas o me­nos ha­ce lo que es­pe­ra­ba que se pue­die­ra lo­gra­r, pe­ro le fal­tan arre­glo­s.

Pa­ra ver el có­di­go, va­yan a su ho­me pa­ge: http://­de­vi­cen­zo­.­google­co­de.­com

¿Cuanto browser entra en 128 líneas de código?

Lo que me gus­ta­ba de ese bro­w­ser de 42 lí­neas era que no era el ejem­plo tí­pi­co, don­de me­ten una vis­ta de We­bkit en una ven­ta­na, car­gan una pá­gi­na y te tra­tan de con­ven­cer de que son unos ba­na­na­s. Esa ver­sión son 7 lí­nea­s:

import sys
from PyQt4 import QtGui,QtCore,QtWebKit
app=QtGui.QApplication(sys.argv)
wb=QtWebKit.QWebView()
wb.setUrl(QtCore.QUrl('http://www.python.org'))
wb.show()
sys.exit(app.exec_())

O 6 si lo so­por­ta­ra un po­co más feo.

¡Pe­ro igua­l, el de 42 se veía úti­l!

This 42-line web browser, courtesy of #python and #qt -- http... on Twitpic

Esos bo­to­nes que se ven fun­cio­na­ban co­rrec­ta­men­te, ha­bi­li­tan­do y des­ha­bi­li­tan­do­se en el mo­men­to co­rrec­to, la en­tra­da de URL cam­bia­ba cuan­do ha­cías cli­ck en un li­nk, y otras co­si­tas así.

Ahí de­ci­dí em­pe­zar un pe­que­ño pro­yec­to in­ter­mi­ten­te de co­de gol­f: me­ter el me­jor bro­w­ser que pue­da en 128 lí­neas de có­di­go (sin con­tar co­men­ta­rios ni blan­co­s), usan­do so­lo Py­Q­t4.

Eso tie­ne un pro­pó­si­to úti­l: siem­pre sos­pe­ché que si uno asu­me Py­Qt co­mo par­te del sis­te­ma ba­se, la ma­yo­ría de las apli­ca­cio­nes en­tra­rían en diske­ttes. Es­ta en­tra unas 500 ve­ces en uno de 1.44MB (¡a­sí que po­dés usar los de 360 de co­m­mo­do­re sin du­pli­disk!)

has­ta aho­ra van 50 lí­nea­s, y tie­ne los si­guien­tes fea­tu­res:

  • Zoom in (C­­tr­­l++)

  • Zoom out (C­­tr­­l+-)

  • Re­set Zoom (C­­tr­­l+=)

  • Bus­­car (C­­tr­­l+­­F)

  • Es­­co­n­­der bús­­que­­da (Es­­c)

  • Bo­­­to­­­nes de atrá­s/a­­de­­lan­­te y re­­ca­r­­gar

  • En­­tra­­da de URL que coi­n­­ci­­de con la pá­­gi­­na + au­­to­­­co­m­­ple­­ta­­do des­­de la his­­to­­­ria + arre­­gla la URL pues­­ta a ma­no (a­­gre­­ga http://, esas co­­sas)

  • Plu­­gins (i­n­­cluí­­do fla­s­h, que hay que ba­­jar apa­r­­te ;-)

  • El tí­­tu­­lo de la ven­­ta­­na mues­­tra el tí­­tu­­lo de la pá­­gi­­na (sin pro­­­pa­­gan­­da del bro­­w­se­­r)

  • Ba­­rra de pro­­­gre­­so pa­­ra la ca­r­­ga de la pá­­gi­­na

  • Ba­­rra de es­­ta­­do que mues­­tra el des­­tino de los li­nks cuan­­do pa­sas el mou­­se

  • To­­­ma una URL en la lí­­nea de co­­­man­­do (o abre http://­­p­­y­­tho­­n.org

  • Mu­l­­ti­­pla­­ta­­fo­r­­ma (fun­­cio­­­na do­n­­de fun­­cio­­­na QtWe­­bKi­­t)

Fal­tan ta­bs y so­por­te de pro­x­y. Es­pe­ro que lle­ven unas 40 lí­neas má­s, pe­ro creo que ya es el más ca­paz de to­dos es­tos bro­w­sers de ejem­plo.

El có­di­go­... no es tan te­rri­ble. Uso mu­chos lamb­da­s, y los ar­gu­men­tos ke­yword de Py­Qt pa­ra co­nec­tar se­ña­le­s, que ha­cen que al­gu­nas lí­neas sean muy lar­ga­s, pe­ro no muy di­fí­ci­le­s. Se po­dría achi­car bas­tan­te to­da­vía!

Aquí es­tá en ac­ció­n:

Y aquí es­tá el có­di­go:

#!/usr/bin/env python
"A web browser that will never exceed 128 lines of code. (not counting blanks)"

import sys
from PyQt4 import QtGui,QtCore,QtWebKit

class MainWindow(QtGui.QMainWindow):
    def __init__(self, url):
        QtGui.QMainWindow.__init__(self)
        self.sb=self.statusBar()

        self.pbar = QtGui.QProgressBar()
        self.pbar.setMaximumWidth(120)
        self.wb=QtWebKit.QWebView(loadProgress = self.pbar.setValue, loadFinished = self.pbar.hide, loadStarted = self.pbar.show, titleChanged = self.setWindowTitle)
        self.setCentralWidget(self.wb)

        self.tb=self.addToolBar("Main Toolbar")
        for a in (QtWebKit.QWebPage.Back, QtWebKit.QWebPage.Forward, QtWebKit.QWebPage.Reload):
            self.tb.addAction(self.wb.pageAction(a))

        self.url = QtGui.QLineEdit(returnPressed = lambda:self.wb.setUrl(QtCore.QUrl.fromUserInput(self.url.text())))
        self.tb.addWidget(self.url)

        self.wb.urlChanged.connect(lambda u: self.url.setText(u.toString()))
        self.wb.urlChanged.connect(lambda: self.url.setCompleter(QtGui.QCompleter(QtCore.QStringList([QtCore.QString(i.url().toString()) for i in self.wb.history().items()]), caseSensitivity = QtCore.Qt.CaseInsensitive)))

        self.wb.statusBarMessage.connect(self.sb.showMessage)
        self.wb.page().linkHovered.connect(lambda l: self.sb.showMessage(l, 3000))

        self.search = QtGui.QLineEdit(returnPressed = lambda: self.wb.findText(self.search.text()))
        self.search.hide()
        self.showSearch = QtGui.QShortcut("Ctrl+F", self, activated = lambda: (self.search.show() , self.search.setFocus()))
        self.hideSearch = QtGui.QShortcut("Esc", self, activated = lambda: (self.search.hide(), self.wb.setFocus()))

        self.quit = QtGui.QShortcut("Ctrl+Q", self, activated = self.close)
        self.zoomIn = QtGui.QShortcut("Ctrl++", self, activated = lambda: self.wb.setZoomFactor(self.wb.zoomFactor()+.2))
        self.zoomOut = QtGui.QShortcut("Ctrl+-", self, activated = lambda: self.wb.setZoomFactor(self.wb.zoomFactor()-.2))
        self.zoomOne = QtGui.QShortcut("Ctrl+=", self, activated = lambda: self.wb.setZoomFactor(1))
        self.wb.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, True)

        self.sb.addPermanentWidget(self.search)
        self.sb.addPermanentWidget(self.pbar)
        self.wb.load(url)


if __name__ == "__main__":
    app=QtGui.QApplication(sys.argv)
    if len(sys.argv) > 1:
        url = QtCore.QUrl.fromUserInput(sys.argv[1])
    else:
        url = QtCore.QUrl('http://www.python.org')
    wb=MainWindow(url)
    wb.show()
    sys.exit(app.exec_())

The Outhouse and the Mall

Wear­ing the soft­ware en­gi­neer's hat: Code is the most triv­ial and the least im­por­tant part of a fea­ture.

—Michael Ia­trou

Michael tweet­ed that, I replied, he replied, but what the heck, I think some­times things can be bet­ter ex­plained in more than 140 char­ac­ter­s, thus this post [1].

So, why the mall and the out­house? Be­cause when we talk about soft­ware and code and fea­tures, we are not all talk­ing about the same thing. Imag­ine if I told you that bricks are triv­ial. Af­ter al­l, they have ex­ist­ed in their cur­rent form for thou­sands of years, they are pret­ty sim­ple to man­u­fac­ture, and have no in­ter­est­ing fea­tures, re­al­ly, ex­cept a cer­tain re­sistence.

Now, sup­pose you are build­ing an out­house. Since you are a fun­ny guy, you want to build an ac­tu­al brick out­house so you will use bricks to do it.

Now, since bricks are so bor­ing, you may feel com­pelled to be­lieve bricks are the least im­por­tant part of your ed­i­fice, and that the over­all de­sign is more im­por­tan­t. Should you carve a moon-shaped hole in the door? How deep should the la­trine be?

How­ev­er, that po­si­tion is fa­tal­ly flawed, since if you ig­nore those triv­ial, bor­ing brick­s, all you have is shit in a hole in the ground. That is be­cause you are con­sid­er­ing the bricks as just a mean to your end. You on­ly care about the bricks in­so­far as they help you re­al­ize your grand out­house vi­sion. I am here to tell you that you are wrong.

The first way in which you are wrong is in that ar­ti­fi­cial sep­a­ra­tion be­tween means and end­s. Ev­ery­one is fa­mil­iar with the eth­i­cal co­nun­drum about whether the ends jus­ti­fy the mean­s, but that's garbage. That'swhat you say when you try to con­vince your­self that do­ing things hap­haz­ard­ly is ok, be­cause what you do is just the means to what­ev­er oth­er thing is the end. Life is not so eas­i­ly di­vid­ed in­to things that mat­ter and things that don't.

Your work, your cre­ation is not just some ide­al iso­lat­ed end to­wards which you trav­el across a sea of dirty mean­s, try­ing to keep your sil­ver ar­mour clean. It's one whole thing. You are cre­at­ing the mean­s, you are cre­at­ing your goal, you are re­spon­si­ble for both, and if you use shod­dy brick­s, your out­house should shame you.

In the same way, if you do crap­py code, your fea­ture is de­meaned. It may even work, but you will know it's built out of crap. You will know you will have to fix and main­tain that crap for years to come, or, if you are luck­y, ru­in your kar­ma by dump­ing it on the head of some poor suck­er who fol­lows your step­s.

I am pret­ty much a ma­te­ri­al­ist. If you re­move the code, you don't have a fea­ture, or soft­ware, you have a con­cep­t, maybe an idea, per­haps a de­sign (or maybe not) but cer­tain­ly not soft­ware, just like you don't have a brick out­house with­out pil­ing some damn bricks one on top of the oth­er.

I al­ways say, when I see some­one call­ing him­self a soft­ware en­gi­neer, that I am mere­ly a soft­ware car­pen­ter. I know my tool­s, I care about them, I use them as well as I can ac­cord­ing to my lights [2] and I try to pro­duce as good a piece of fur­ni­ture as I can with what I am giv­en.

This tends to pro­duce hum­ble soft­ware, but it's soft­ware that has one re­deem­ing fea­ture: it knows what it should do, and does it as well as I can make it. For ex­am­ple, I wrote rst2pdf. It's a pro­gram that takes some sort of tex­t, and pro­duces PDF files. It does that as well as I could man­age. It does noth­ing else. It works well or not, but it is what it is, it has a pur­pose, a de­scrip­tion and a goal, and I have tried to achieve that goal with­out em­bar­ras­ing my­self.

My pro­grams are out­hous­es, made of care­ful­ly se­lect­ed and con­sid­ered brick­s. They are not fan­cy, but they are what they are and you know it just by look­ing at them. And if you ev­er need an out­house, well, an out­house is what you should get.

Al­so, peo­ple tend to do weird stuff with them I nev­er ex­pect­ed, but that's just the luck of the anal­o­gy.

But why did I men­tion malls in the ti­tle? Be­cause malls are not out­hous­es. Malls are not done with a goal by them­selves be­yond mak­ing mon­ey for its builder­s. The ac­tu­al func­tion of a piece of mall is not even known when it's be­ing built. Will this be a Mc­Don­ald­s, or will it be a com­ic book store? Who knows!

A mall is built quick­ly with what­ev­er makes sense mon­ey­wise, and it should look bland and recog­nis­able, to not scare the herd. It's a build­ing made for pedes­tri­an­s, but it's in­tend­ed to con­fuse them and make the path form A to B as long and me­an­der­ing as pos­si­ble. The premis­es on which its de­sign is based are all askew, cor­rupt­ed and self­-­con­tra­dict­ing.

They al­so give builders a chance to make lots of mon­ey. Or to lose lots of mon­ey.

Nowa­days, we live in an age of mall soft­ware. Peo­ple build star­tup­s, get fi­nanc­ing, build crap­py soft­ware and some­times they hit it big (Twit­ter, Face­book) or, more like­ly, fade in­to ob­scu­ri­ty leav­ing be­hind noth­ing at al­l, ex­cept mon­ey lost and sad pro­gram­mers who spent nights cod­ing stuff noone will ev­er see or use, and not much else.

Far from me say­ing star­tups are not a no­ble or wor­thy en­deav­our. They are! It's just that peo­ple who work on them should re­al­ize that they are not build­ing soft­ware. That's why code does­n't look im­por­tant to them, be­cause they are ac­tu­al­ly sell­ing eye­balls to ad­ver­tis­er­s, or col­lect­ed per­son­al da­ta from their users to who­ev­er buys that, or cap­tive pub­lic for game de­vel­op­er­s, or what­ev­er your busi­ness mod­el says (if you have one!).

They are build­ing mall­s, where the val­ue is not in the build­ing, which is pret­ty ghast­ly and use­less by it­self, but on the peo­ple in it, those who rent space in the mal­l, those who will use the mal­l, the soft­ware, the so­cial net­work, what­ev­er it is you are build­ing.

Twit­ter is not soft­ware, Face­book is not soft­ware. If they were, iden­ti.­ca and di­as­po­ra would be big­ger! What they are is peo­ple in one place, like a mall is not a re­al build­ing, but a col­lec­tion of peo­ple un­der a roof.

So, there is noth­ing wrong with build­ing mall­s. Just re­mem­ber that your ends and your means are one and a whole, that code is im­por­tan­t, that with­out code Face­book and Twit­ter don't work, and that with­out peo­ple they are a bad­land, and know what you are do­ing.

Be­cause the on­ly hard thing in life is know­ing what you want to do. The rest is the easy part. And be­cause malls with­out toi­lets suck.

Charla: docutils / rst y sus amigos

Again, span­ish on­ly be­cause it's a video... in span­ish.

Re­sul­ta que me olvidé que sí habían graba­do mi char­la de do­cu­tils y com­pañi­a. Gra­cias a Ger­mán por hac­erme acor­dar y mostrarme adonde es­taba!

Y ... acá es­tá:


Contents © 2000-2023 Roberto Alsina