Ruby и крассивый код #5: Рекомендации по стилю

февраля 24, 2011  |  Published in Ruby, Основы  |  26 Comments

samurai
Сегодня немного налажал с кодом и решил написать статью так сказать для закрепления представлений о хорошем коде в голове, ну и разумеется для того, чтобы поделиться имеющимися бедными познаниями с другими новичками. Более-менее опытным разработчикам все это уже, наверняка, известно, поэтому, если вы себя таковым считаете — можете не читать.

1. Используйте && и || вместо AND или OR.(в комментариях подсказывают, что они все же отличаются разным приоритетом).

Вместо:

if arr.size <= 10 OR (arr.size >= 5 AND arr[1] == 'first')

Лучше:

if arr.size <= 10 || (arr.size >= 5 && arr[1] == 'first')

2. Используйте для указания уровня вложенности кода двойные пробелы, вместо табуляций

Вместо:

class Example
       def ex_method(arg)
              puts "something"
              arg.each do |a|
                   puts a
              end
       end
end

Лучше:

class Example
  def ex_method(arg)
    puts "something"
    arg.each do |a|
      puts a
    end
  end
end

3. Используйте осмысленные имена методов, переменных и т.д.

Вместо:

var = 36.6
m = %w{January February March April May June July Augest September October November December}

Лучше:

body_temperature = 36.6
months = %w{January February March April May June July Augest September October November December}

Bang! — методы — это методы, которые изменяют состояние объекта. не забывайте, что имена таких методов должны, как уже сложилась традиция заканчиваться знаком восклицания.

Знаком вопроса заканчиваются имена методов, которые в качестве результата работы возвращают булевый тип: true или false. В PHP такие методы еще именуют как is_<�имя метода>.

4. Страйтесь максимально отказаться от циклов в пользу итераторов. Итераторы — это Ruby Way!

Вместо:

for i in args do
  puts i
end

Лучше:

args.each{|i| puts i}

5. Используйте фигурные скобки «{}» только для обрамления однострочных блоков кода.

Вместо:

args.each {|user|
  user.posts.each {|post|
    post.comments.each{|comment|
      puts comment
    }
  }
}

Лучше:

args.each do |user|
  user.posts.each do |post|
    post.comments.each do |comment|
      puts comment
    end
  end
end

Вместо:

1000.times do |num| puts num; end

Лучше:

1000.times {|num| puts num}

6. Оставляйте пробелы между операторами

Вместо:

num = 12/3*(34-5)+args.count/4

Лучше:

num = 12 / 3 * (34 - 5) + args.coutn / 4

Вместо:

if a>b&&c<b

Лучше:

if a > b && c < b

7. В Ruby все имеет значение true или false. Отказывайтесь от использования if для возврата true или false:

Вместо:

if a > b && c < b
  true
else
  false
end

Лучше:

a > b && c < b

8. Используйте одноименные операторы

Вместо:

a > b && c < b

Лучше:

a > b && b > c

9. Используйте параллельное присваивание

Вместо:

a = 10
b = 15
c = 20

Лучше:

a, b, c = 10, 15, 20
a, b, *c = [...] # если в коллекции больше 3 элементов, то с заберет на себя все оставшиеся после a и b значения.

10. Форматируйте или разбивайте длинные строки

Вместо:

@somethong = do_somethong :conditions => {:size => '23', :weight=> '56', :height => '165', :eyes_color=> 'blue', ...}

Лучше:

@somethong = do_something :conditions => {
                       :size => '23',
                       :weight=> '56',
                       :height => '165',
                       :eyes_color=> 'blue',
                       ...}

Старайтесь избегать длинных строк кода и разбивать их на более короткие или форматировать в вышеприведенный способ. Оптимальной длиной считается строка кода до 70-80 символов.

Используйте специальный синтаксис для многострочных литералов:

Вместо:

@welcome_msg = "Ruby is…\nA dynamic, open source programming language \nwith a focus on simplicity and productivity.\nIt has an elegant syntax that is natural\nto read and easy to write."

Лучше:

@welcome_msg = <<msg

===== WELCOME =====
Ruby is…
A dynamic, open source programming language
with a focus on simplicity and productivity.
It has an elegant syntax that is natural
to read and easy to write.
msg

11. Оператор return не является обязательным, однако его использование является хорошим стилем

def name
  @name
end

Однако при написании более сложных и крупных по размеру методов желательно использовать return для явного указания значения, которое будет возвращено.

12. Минимизируйте код в методах и блогах

Большие методы следует разбивать на более мелкие, это будет способствовать повторному использованию кода (принципу DRY), а также сделает код более читабельным.

Вместо:

def reverse_qube_diff(a)
  [a, a.to_s.reverse.to_i, a - a.to_s.reverse.to_i, (a - a.to_s.reverse.to_i) ** 3]
end

Лучше:

def num_reverse(n)
  n.to_s.reverse.to_i
end

def qube(n)
  n**3
end

def reverse_qube_diff(a)
  [a, num_reverse(a), a - num_reverse(a), qube(a-num_reverse(a))]
end

Пример не совсем хорошо показывает пользу такого подхода, но это первое, что пришло мне в голову. Если у вас есть пример лучше — напишите его в комментариях и я добавлю его в статью. Оптимальный размер метода до 7 строк. Это не означает, что вы должны все ваши методы разбивать до такой степени, главным здесь является выноска используемого несколько раз кода в отдельные метода, а также выноска крупных блоков кода.

13. Комментируйте код
Часто бывает сложно понять даже собственный код, который писался всего-то неделю назад. Для того, чтобы быстро вспомнить, что же вы там воротили, и чтобы программист работающий с вашим кодом мог быстрее войти в суть творящегося, следует обильно комментировать код. «Обильно» не значит, что закомментирован должен быть каждый метод, комментируйте, в первую очередь, API для пользователей вашего кода, методы, которые не используются на прямую могут документироваться очень кратко в формате «для себя». Небольшие, простые методы не следует документировать, это лишняя трата времени.

# Меняет порядок разрядов числа на обратный
def num_reverse(n)
  n.to_s.reverse.to_i
end

def qube(n)
  n**3
end

#Возвращает массив:
#0 => число, аргумент метода
#1 => числом производное от аргумента метода с обратным порядком разрядов
#2 => разница между аргументом и производним от него числом с обратным подядком разрядов
#3 => [2] возведенное в куб
def reverse_qube_diff(a)
  [a, num_reverse(a), a - num_reverse(a), qube(a-num_reverse(a))]
end

14. Не бойтесь синтаксического сахара

Вместо:

if @args.empty?
  puts "Error: Array is empty"
else
  @args.each{|a| puts a}
end

Лучше:

puts @args.empty? ? "Error: Array is empty" : @args.each{|a| a }

Используйте новый (начиная с Ruby 1.9.х) синтаксис для символов-ключей:

Вместо:

args = {:sunday => 127, :monday => 245, :tuesday => 312, :wednesday => 347, :thursday => 320, :friday => 250, :saturday => 0}

Лучше:

args = {sunday: 127, monday: 245, tuesday: 312, wednesday: 347, thursday: 320, friday: 250, saturday: 0}

15. Определяйте собственный метод to_s
Метод to_s имеется у каждого объекта и используется для строкового представления объекта. Метод puts использует метод to_s для получения строкового представления, которое и будет напечатано. Пример:

args = MyHashImp.new sunday: 127, monday: 245, tuesday: 312, wednesday: 347, thursday: 320, friday: 250, saturday: 0

class MyHashImp < Hash
  def initialize hash
    @hash = hash
  end

  def to_s
    output = ""
    max = 0

    @hash.each_key do |k|
      max < k.size ? max = k.size : false
    end

    @hash.each{|k, v|
      output = output + "#{k} #{" " * (max - k.size)} store  #{v}\n"}
      return output
    end
end

puts args

sunday        store  127
monday       store  245
tuesday       store  312
wednesday  store  347
thursday      store  320
friday           store  250
saturday      store  0

16. Используйте пустые строки для разделения фрагментов кода
Используйте пустые строки для разделения, например, объявления методов, инициации переменных, блоков кода.

Вместо:

class MyHashImp < Hash
  def initialize hash
    @hash = hash
  end
  def to_s
  output = ""
  max = 0
  @hash.each_key do |k|
    max < k.size ? max = k.size : false
  end
  @hash.each{|k, v| output = output + "#{k} #{" " * (max - k.size)} store  #{v}\n"}
    return output
  end
end

Лучше:

class MyHashImp < Hash
  def initialize hash
    @hash = hash
  end

  def to_s
    output = ""
    max = 0

    @hash.each_key do |k|
      max < k.size ? max = k.size : false
    end

    @hash.each do |k, v|
      output = output + "#{k} #{" " * (max - k.size)} store  #{v}\n" 
    end

    return output
  end
end

Таким образом вы разбиваете код на логические фрагменты и улучшаете его читабельность. Это особенно актуально для крупных блоков кода и методов.

17. Основной код должен относиться на первом месте

Вместо:

puts @args.empty? ? "Error: Array is empty" : @args.each{|a| a }

Лучше:

puts !@args.empty? ? @args.each{|a| a } : "Error: Array is empty"

Сюда же можно отнести использование unless вместо if. Лично мне больше нравится !<условие>, вместо unless.

18. Используйте CASE вместо группы IF или IF — ELSIF когда проверяется переменная

Вместо:

puts 1 if a = 1
puts 2 if a = 2
puts 'indefinite' if a = 10_000

Или:

if a = 1
  puts 1
elsif a = 2
  puts 2
elsif a = 10_000
  puts "indefinite"
end

Лучше:

puts case a
when 1
  1
When 2
  2
when 10_000
  "indefinite"
end

19. Разбивайте регулярные выражения на более мелкие шаблоны

Вместо:

tel = '38-096-2870-430'
pattern = /^([\d]{2})-([\d]{3})-([\d]{4}-[\d]{3})$/
m = tel.match(pattern)

Лучше:

tel = '38-096-2870-430'
state = /([\d]{2})-/
station = /([\d]{3})-/
phone = /([\d]{4}-[\d]{3})/

pattern = /^#{state}#{station}#{phone}$/
m = tel.match(pattern)

Разбиение шаблона на логические части позволяет легче воспринимать синтаксис регулярных выражений, а также несколько упрощает их разработку и способствует чистоте кода, хотя количество строк и увеличилось.

Статья будет пополняться новыми рекомендациями. Пишите свои рекомендации в комментариях, и они будут добавлены в статью.

Некоторые из приведенных выше рекомендаций являются де-факто стандартом написания кода.

P.S. Хочу обидеть тех, кто никогда не читает комментарии, там сейчас много интересных моментов.

Рубрика : Ruby и красивый код

Лучшая благодарность автору — ваши комментарии!

Tags: , ,

Responses

  1. Валентин says:

    февраля 24, 2011 at 23:27 (#)

    1. Используйте && и || вместо AND или OR.

    Вместо:

    1
    if arr.size = 5 AND arr[1] == ‘first’)
    Лучше:

    1
    if arr.size = 5 && arr[1] == ‘first’)

    вот это очень спорный момент , зависит от логики того что вы хотите получить
    операторы || — OR и && — AND имеют разную степень связывания и ваш код в примере не равноценен

    на хабре об этом уже писали

  2. exromany says:

    февраля 24, 2011 at 23:43 (#)

    в примере к 15 пункту переменная min не используется

  3. says:

    февраля 24, 2011 at 23:43 (#)

    1. Используйте && и || вместо AND или OR.
    Это разные операторы и разные функции выполняют. Первый совет бред

    14. Не бойтесь синтаксического сахара
    ruby-1.8.7-p330 :002 > args = {sunday: 127}
    SyntaxError: compile error
    (irb):2: odd number list for Hash
    args = {sunday: 127}
    ^
    (irb):2: syntax error, unexpected ‘:’, expecting ‘}’
    args = {sunday: 127}
    ^
    from (irb):2
    oops?

  4. exromany says:

    февраля 24, 2011 at 23:54 (#)

    в 17 пункте можно так:
    puts @args.any? ? @args.each{|a| a } : «Error: Array is empty»
    в 8 пункте: c < b > c, но c < b >= c
    в 9 пункте: можно обойтись без квадратных скобок

  5. exromany says:

    февраля 24, 2011 at 23:57 (#)

    будь неладны ббкоды )
    в 8 пункте

     c  c, но c = c
  6. exromany says:

    февраля 25, 2011 at 00:00 (#)

    (c меньше b) и (b больше c) не эквивалентны. Эквивалентна будет запись (b больше или равно c)

  7. Валентин says:

    февраля 25, 2011 at 00:02 (#)

    args = {sunday: 127}

    такой сахар появился начиная с Ruby 1.9

  8. Damir says:

    февраля 25, 2011 at 00:56 (#)

    >> args = {sunday: 127}
    работает в ветке ruby 1.9

  9. c0va23 says:

    февраля 25, 2011 at 03:04 (#)

    [b]shiroginne[/b], 14-ый совет будет работать в 1.9.2, вроде даже было в этом блоге.

  10. says:

    февраля 25, 2011 at 05:01 (#)

    «1. Используйте && и || вместо AND или OR.
    Это разные операторы и разные функции выполняют. Первый совет бред»
    Почему же?

  11. says:

    февраля 25, 2011 at 08:44 (#)

    По пункту 11 хочу заметить, что в Ruby стоит явно использовать оператор return, когда у вас может быть несколько точек выхода из метода.

  12. says:

    февраля 25, 2011 at 08:48 (#)

    И еще. Может стоит форматировать хэши в несколько строк, вот так:

    args = {
    sunday: 127,
    monday: 245,
    tuesday: 312,
    wednesday: 347,
    thursday: 320,
    friday: 250,
    saturday: 0
    }

    Как думаете?

  13. says:

    февраля 25, 2011 at 09:05 (#)

    Вертикальное форматирование кода и переменные — атавизм от языков низкого уровня: C, Pascal и.д.

    Ruby возвращает результат выражения которым можно пользоваться, раньше надо было сохранять промежуточные результаты. Переменные нужны только для параметров функциий и блоков. Чем уже них область видимости тем скорей они будут удалены из памяти.

    У человека два глаза и расположены они горизонтально. Широкий текст читать удобней с помощью переферийного зрения. Переверните монитор вертикально или сузьте какой либо текст и почувствуйте разничу восприятия и скорочтения.

    1. Согласен лучше иместь во всем коде один подход. Это упрощает понимание.

    10-13. Полностью не согласен и рекомендую делать с точностью до наоборот. На основании вышеизложенных причин «раздувания» текста.
    return стоит использовать если логика возвращения результата неочевидна, но тут своит задуматься о причинах возникновения неоднозначностей.

    18. Пример неудачен. Последовательные if не аналог ifelsif. В первом примере проверяються все условия, во втором они взаимоисключаются.

  14. says:

    февраля 25, 2011 at 10:07 (#)

    4) к сожалению цикли работают быстрее, нежели итераторы.

  15. admin says:

    февраля 25, 2011 at 13:50 (#)

    Роман, позвольте с вами не согласиться. Я несколько интересуюсь юзабилити и в разных авторитетных источниках встречал информацию о том, что текстовые блоки должны быть ограничены по размеру 70-80 символами максимум, чем окно редактора кода не текстовый блок? Это необходимо для того, чтобы сократить движения глазами, от чего глаза устают.

    10, понятно, спорный момент, особенно в том несколько неудачном примере с форматированием хэша, но почему 11 — 13 пункты не нравятся?

    Постфиксный if не то же самое,что и if-elsif-else, это понятно, что if-elsif-else прекращает работу, если на одном из этапов получаем true, однако в конкретно в приведенном примере результат одинаковый. В прочем, думаю, вы правы в своем замечании, сейчас поправлю.

    Andy Да, там пример несколько не удачный с хэшем. Хотя, когда в метод передаются параметры хэшем, то достаточно удобно форматировать подобным образом. По крайней мере я так делаю и видел такой стиль в чужом коде.

    exromany, да, тут комментарий с кодом написать, нужно коды всех символов знать=). Я если вижу какой-то непонятный набор символов в комментарии, то сразу пытаюсь его отредактировать, поэтому на будущее можете писать в удобный способ, я отредактирую, если что.

    От чего же с меньше b и b больше c не эквивалентны? Это пример со строгой проверкой, когда «=» не учитывается. Или я что-то не понимаю?

    Спасибо за найденый артефакт в коде (переменная min)

    9 пункт подправил, спасибо!

    Валентин, спасибо за инфу по поводу && и AND… Чесно признаться, не знал, что они чем-то отличаются. В свою защиту скажу, что с этой рекомендацией я сталкивался уже несколько раз. Сейчас разберусь с различиями и подправлю.

    По поводу синтаксического сахара из 1.9.2. Меня уже не первый раз упрекают, но я сторонник использования новых плюшек и вообще я работаю под 1.9.2. Так что уж извиняйте =)

    Андрей, я еще не видил, чтобы это было аргументом для отказа от итераторов.

    P.S. Где еще можно так много полезного узнать, кроме как в комментариях? Спасибо всем!

  16. admin says:

    февраля 25, 2011 at 13:56 (#)

    exromany

    Итератор .any?

    puts @args.any? ? @args.each{|a| a } : “Error: Array is empty”

    вместо:

    puts !@args.empty? ? @args.each{|a| a } : "Error: Array is empty"

    Все же, не самая лучшая идея, по моему, просто потому, что итераторы предназначены для других целей, а empty? специально на проверку на пустоту. Ну это, конечно, я уже придираюсь =)

  17. potapuff says:

    февраля 25, 2011 at 14:19 (#)

    >> 2. Используйте для указания уровня вложенности кода двойные пробелы, вместо табуляций

    Если ваш редактор не позволяет установить ширину табуляции — выкиньте его!

  18. says:

    февраля 25, 2011 at 15:52 (#)

    > 4) к сожалению цикли работают быстрее, нежели итераторы.

    Даже если это и так, в чем я лично сомневаюсь, то скорость написания программы важнее скорости ее выполнения. Ранняя оптимизация — плохая практика.

    > Если ваш редактор не позволяет установить ширину табуляции – выкиньте его!

    У меня в команде пять человек, каждый установит ширину табуляции [1,2,3,4,5] пробела.
    Кого мне выкинуть в окно первым за такой вот совет? ;-)

    > if vs ifelse приведенном примере результат одинаковый..

    Не одинаковый по времени исполнения. Последовательные if выполнятся всегда, а в взаимоисклюающие не все.

    13-14 я уже объяснил. Код раздувается ввысоту, а это плохо

    Строки по ширине экрана(70-80) — согласен

    Юзабилисты ? Когда я смотрю на интерфейс LinkedIn мне хочеться кого нибуть стукнуть за проделанную работу ;-)

  19. says:

    февраля 25, 2011 at 20:02 (#)

    2) potapuff +1 :)
    Роман, пускай каждый установит ширину табуляции на экране так, как ему это удобно, но в коде это всё равно лучше оставить одним символом (\t). В конце концов, у разных программистов разные по диагонали мониторы.

    4) У переменных внутри циклов область видимости отличается от тех же переменных, но внутри итераторов.

    6) Чисто субъективно мне кажется, что это зависит от текста, окружающего операторы. Иногда подсветка синтаксиса решает и это бессымсленно раздувает строку.

    9) [Немного снобства] Это же вроде параллельное, а не множественное присваивание.

    10) admin, тут Я Вас поддержу, 80 колонок — стандарт де-факто для исходных текстов. Так ещё со времён DOS повелось. К тому же, Роман приводит спорный аргумент, что глаза расположены горизонтально, однако почему же тогда во всех без исключения газетах текст верстается в несколько колонок?

  20. admin says:

    февраля 25, 2011 at 21:47 (#)

    inst, чесно говоря, не знал как оно правильно называется, решил, что название «множественное присваивание» отлично отразит суть, сейчас поправлю.

  21. says:

    февраля 28, 2011 at 07:33 (#)

    «12. Минимизируйте код в методах и блогах»
    наверное в блоКах?!

  22. Sozontov Anton says:

    февраля 28, 2011 at 10:42 (#)

    На счет итераторы vs циклы

    Провел небольшое исследование, из которого следует, что цикл for..in работает немного медленнее, но при этом итераторы более юзабельны.

    require "benchmark"
    include Benchmark
    
    n = 1_000_000
    
    Benchmark.benchmark(" "*7 + CAPTION, 7, FMTSTR) do |x|
      x.report("for:")   { for i in 1..n; a = "1"; end }
      x.report("times:") { n.times do   ; a = "1"; end }
      x.report("each:")  { (1..n).each do; a = "1"; end }
      x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
    end
    

    user system total real
    for: 1.230000 0.000000 1.230000 ( 1.227438)
    times: 1.120000 0.000000 1.120000 ( 1.126706)
    each: 1.150000 0.000000 1.150000 ( 1.141455)
    upto: 1.130000 0.000000 1.130000 ( 1.138592)

    user system total real
    for: 1.150000 0.010000 1.160000 ( 1.176419)
    times: 1.130000 0.000000 1.130000 ( 1.126034)
    each: 1.120000 0.000000 1.120000 ( 1.125477)
    upto: 1.130000 0.000000 1.130000 ( 1.128533)

    user system total real
    for: 1.210000 0.000000 1.210000 ( 1.218572)
    times: 1.140000 0.000000 1.140000 ( 1.145365)
    each: 1.140000 0.000000 1.140000 ( 1.143632)
    upto: 1.140000 0.000000 1.140000 ( 1.143849)

    user system total real
    for: 1.220000 0.000000 1.220000 ( 1.216285)
    times: 1.120000 0.000000 1.120000 ( 1.126992)
    each: 1.150000 0.000000 1.150000 ( 1.147207)
    upto: 1.140000 0.000000 1.140000 ( 1.134411)

    user system total real
    for: 1.180000 0.000000 1.180000 ( 1.182742)
    times: 1.130000 0.000000 1.130000 ( 1.126704)
    each: 1.120000 0.010000 1.130000 ( 1.132910)
    upto: 1.140000 0.000000 1.140000 ( 1.136653)

    user avg

    for: 1.198000
    times: 1.128000
    each: 1.136000
    upto: 1.136000

  23. Sozontov Anton says:

    февраля 28, 2011 at 11:30 (#)

    16.

    Сначала я хотел на этом примере напомнить про метод Object#tap, но он сюда не подходит
    и в процессе получился такой отрефакторенный метод xD.

    class MyHashImp < Hash
      def to_s
        max = self.max { |a,b| a.first.length  b.first.length }.first.length rescue 0
        output = self.map do |k, v|
          "#{k} #{" " * (max - k.size)} store  #{v}\n"
        end.join
      end  
    end
    

    На счет Object#tap. (он же Object#returning в Rails)

    Чтобы не плодить временных переменных можно делать так:

    class User
      def activate!
        self.update_attribute("active", true) #=> true
      end
    end
    
    def activate_current_user_and_return_him
      current_user.tap do |u| 
        u.activate! 
        logger.info "User with id \"#{user.id}\" was activated"
      end #=> current_user
    end
    
    def activate_current_user_and_return_him_without_tap
      current_user.activate! 
      logger.info "User with id \"#{current_user.id}\" was activated"
      current_user
    end
    
    # более удачный пример
    def activate_current_user_and_return_him
      current_user.tap(&:activate!)
    end
    
    

    Описание Object#tap —

  24. Serg says:

    сентября 16, 2012 at 00:58 (#)

    Очень хорошая статья.

    правда есть недочеты:
    1) В 1 пункте — совет не совсем корректен, использовать можно оба варианта, только нужно помнить что and и or имеют ниже приоритет (главное ниже оператора «=»), но это не делает их хуже || и &&. Правильней посоветовать использовать скобки чтоб себя обезопасить от ошибки.

    2) В пункте 11 — насколько я понял, вы хотели подчеркнуть что return можно не! использовать (это тоже своего рода синтаксический сахар), но вышло скорее наоборот. Если добавить пример «как не надо делать», то всё станет на свои места.

    3) В пункте 15 — вот это скорее плохой совет. Затирать встроенные методы может иногда и удобней, чем прописать вручную, или создать отдельный метод, но всегда повышает вероятность ошибок.

    4) В пункте 19 — это может быть полезно при отладке, но создавать лишние переменные просто для того чтобы увеличить читабельность не целесообразно. Лучше просто разбить этот код на разные строки, как в пункте 10.

  25. Duke says:

    октября 22, 2012 at 11:21 (#)

    a, b, c = 10, 15, 20
    a, b, *c = [...] 

    В 9 пункте не произойдёт параллельного присваивания. переменной «а» присвоится массив. Массив нужно распаковать, добавив в начало унарный оператор звёздочку.

    a, b, c = 10, 15, 20
    a, b, *c = *[...] 
  26. Duke says:

    октября 22, 2012 at 11:24 (#)

    Не, наврал… Извиняюсь

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.