Eval tam olarak neden kötülüktür?


141

Lisp ve Scheme programcılarının eval, kesinlikle gerekli olmadıkça bundan kaçınılması gerektiğini söylediklerini biliyorum . Birkaç programlama dili için aynı öneriyi gördüm, ancak henüz kullanımına karşı açık argümanların bir listesini henüz görmedim eval. Kullanımdaki olası sorunların bir hesabını nerede bulabilirim eval?

Örneğin, GOTOyordamsal programlamadaki sorunları biliyorum (programları okunamaz ve bakımı zorlaştırıyor, güvenlik sorunlarını bulmayı zorlaştırıyor, vb.), Ama hiçbir zaman argümanları görmedim eval.

İlginçtir, aynı argümanlar GOTOsürekliliklere karşı geçerli olmalıdır, ancak şemaların, örneğin, sürekliliklerin "kötü" olduğunu söylemeyeceğini görüyorum - bunları kullanırken dikkatli olmalısınız. Süreklilik kullanarak kod üzerine göre kod kullanarak kaşlarını çatmak çok daha muhtemeldir eval(görebildiğim kadarıyla - ben yanlış olabilir).


5
eval kötü değil, ama kötü ne eval
Anurag

9
@yar - Bence yorumunuz çok tek bir gönderi-nesne-merkezli dünya görüşüne işaret ediyor. Muhtemelen çoğu dil için geçerlidir, ancak yöntemlerin sınıflara ait olmadığı Common Lisp'te ve sınıfların yalnızca Java birlikte çalışma işlevleri ile desteklendiği Clojure'da daha da farklı olacaktır. Jay bu soruyu herhangi bir yerleşik sınıf veya yöntem nosyonuna sahip olmayan Şema olarak etiketledi (çeşitli OO formları kütüphaneler olarak mevcuttur).
Zak

3
@Zak, haklısın, sadece bildiğim dilleri biliyorum, ama Stilleri kullanmadan bir Word belgesiyle çalışsan bile KURU değilsin. Demek istediğim teknolojiyi tekrar etmemek için kullanmaktı. OO evrensel değil, gerçek ...
Dan Rosenstark

4
Clojure kullanıcılarının burada yayınlanan mükemmel cevaplara maruz kalmaktan fayda sağlayabileceğine inandığım için bu soruya clojure etiketini ekleme özgürlüğünü aldım.
Michał Marczyk

... Eh, Clojure için en az bir ek neden daha geçerlidir: ClojureScript ve türevleriyle uyumluluğu kaybedersiniz.
Charles Duffy

Yanıtlar:


148

Kullanmamanın birkaç nedeni vardır EVAL.

Yeni başlayanların ana nedeni: buna ihtiyacınız yok.

Örnek (Common Lisp olduğu varsayılarak):

Bir ifadeyi farklı işleçlerle DEĞERLENDİRİN:

(let ((ops '(+ *)))
  (dolist (op ops)
    (print (eval (list op 1 2 3)))))

Daha iyi şöyle yazılır:

(let ((ops '(+ *)))
  (dolist (op ops)
    (print (funcall op 1 2 3))))

Lisp öğrenen yeni başlayanların ihtiyaç duyduklarını düşündükleri çok sayıda örnek vardır EVAL, ancak buna ihtiyaç duymazlar - çünkü ifadeler değerlendirilir ve biri de işlev bölümünü değerlendirebilir. Çoğu zaman kullanımı EVALdeğerlendiricinin anlaşılmadığını gösterir.

Makrolarda da aynı sorun var. Yeni başlayanlar genellikle makrolar yazarlar, burada işlevler yazmaları gerekir - makroların gerçekte ne için olduğunu anlamadı ve bir işlevin zaten işi yaptığını anlamadı.

İşin kullanımı genellikle yanlış bir araçtır EVALve genellikle yeni başlayanın normal Lisp değerlendirme kurallarını anlamadığını gösterir.

Eğer gerek düşünüyorsanız EVAL, o zaman böyle olmadığını şeyi kontrol FUNCALL, REDUCEveya APPLYbunun yerine kullanılabilir.

  • FUNCALL - bağımsız değişkenleri olan bir işlevi çağırır: (funcall '+ 1 2 3)
  • REDUCE - değerler listesindeki bir fonksiyonu çağırmak ve sonuçları birleştirmek: (reduce '+ '(1 2 3))
  • APPLY- argüman olarak listelendiği bir işlevi çağırmak: (apply '+ '(1 2 3)).

S: Gerçekten eval'e ihtiyacım var mı veya derleyici / değerlendirici gerçekten istediğim şeyi yapıyor mu?

EVALBiraz daha gelişmiş kullanıcılar için kaçınmanın ana nedenleri :

  • kodunuzun derlendiğinden emin olmak istersiniz, çünkü derleyici kodu birçok sorun için kontrol edebilir ve daha hızlı kod üretir, bazen ÇOK ÇOK ÇOK (bu faktör 1000 ;-)) daha hızlı kod

  • oluşturulan ve değerlendirilmesi gereken kod mümkün olduğunca erken derlenemez.

  • keyfi kullanıcı girişinin değerlendirilmesi güvenlik sorunlarını açar

  • ile değerlendirmenin bazı kullanımı EVALyanlış zamanda olabilir ve derleme problemleri yaratabilir

Son noktayı basitleştirilmiş bir örnekle açıklamak için:

(defmacro foo (a b)
  (list (if (eql a 3) 'sin 'cos) b))

Yani, ya ilk parametreye dayalı bir makro yazmak isteyebilirsiniz SINveya COS.

(foo 3 4)yapar (sin 4)ve (foo 1 4)yapar (cos 4).

Şimdi sahip olabiliriz:

(foo (+ 2 1) 4)

Bu istenen sonucu vermez.

Daha sonra FOOdeğişkeni DEĞERLENDİRerek makroyu onarmak isteyebilir :

(defmacro foo (a b)
  (list (if (eql (eval a) 3) 'sin 'cos) b))

(foo (+ 2 1) 4)

Ama sonra bu hala çalışmıyor:

(defun bar (a b)
  (foo a b))

Değişkenin değeri derleme zamanında bilinmemektedir.

Kaçınılması gereken genel bir önemli neden EVAL: genellikle çirkin hack'ler için kullanılır.


3
Teşekkürler! Son noktayı anlamadım (yanlış zamanda değerlendirme?) - coud biraz detaylandırır mısın lütfen?
Jay

41
+1 bu gerçek cevaptır - insanlar evalsadece yapmak istedikleri şeyleri yapmak için belirli bir dil veya kütüphane özelliği olduğunu bilmedikleri için geri çekilirler. JS benzer bir örnek: Dinamik bir ad kullanarak bir nesneden bir özellik almak istiyorum, bu yüzden yazmak: eval("obj.+" + propName)ne zaman yazmış olabilir obj[propName].
Daniel Earwicker

Ne demek istediğini anlıyorum, Rainer! Thansk!
Jay

@Daniel: "obj.+"? Son kontrol ettiğimde, +JS'de nokta referansları kullanılırken geçerli değil.
Merhaba71

2
@ Daniel muhtemelen beklendiği gibi çalışması gereken eval ("obj." + PropName) anlamına geliyordu.
22'de claj

41

eval(herhangi bir dilde), elektrikli testere kötü olmadığı gibi kötü değildir. Bu bir araçtır. Yanlış kullanıldığında uzuvları koparabilen ve (mecazi olarak konuşan) içlerinden ayırabilen güçlü bir araç olur, ancak aynı şey bir programcının araç kutusundaki birçok araç için de söylenebilir:

  • goto ve arkadaşlar
  • kilit tabanlı diş açma
  • continuations
  • makrolar (hijyenik veya diğer)
  • işaretçileri
  • yeniden başlatılabilir istisnalar
  • kendini değiştiren kod
  • ... ve binlerce oyuncu.

Kendinizi bu güçlü, potansiyel olarak tehlikeli araçlardan herhangi birini kullanmak zorunda bulursanız, kendinize üç kez "neden?" bir zincir. Örneğin:

"Neden kullanmak zorundayım eval?" "Foo yüzünden." "Foo neden gerekli?" "Çünkü ..."

Bu zincirin sonuna gelirseniz ve araç hala yapılacak doğru şey gibi görünüyorsa, yapın. Cehennemden kurtulun. Cehennemi test et. Doğruluk ve güvenliği tekrar tekrar tekrar kontrol edin. Ama yap.


Teşekkürler - daha önce eval hakkında duyduğum bu ("kendinize nedenini sorun"), ancak potansiyel sorunların ne olduğunu henüz duymadım veya okumadım. Şimdi buradaki cevaplardan ne olduklarını görüyorum (güvenlik ve performans sorunları).
Jay

8
Ve kod okunabilirliği. Eval, kod akışını tamamen vidalayabilir ve anlaşılmaz hale getirebilir.
SADECE benim doğru GÖRÜŞÜM

Listenizde neden "kilit tabanlı iş parçacığı" [sic] olduğunu anlamıyorum. Kilitleri içermeyen eşzamanlılık biçimleri vardır ve kilitlerle ilgili sorunlar genellikle iyi bilinir, ancak kilitleri "kötülük" olarak kullandığını kimsenin tanımladığını hiç duymadım.
asveikau

4
asveikau: Kilit tabanlı iş parçacığı doğru almak çok zor (kilitleri kullanarak üretim kodunun% 99,44'ünün kötü olduğunu tahmin ediyorum). Oluşturmaz. "Çok iş parçacıklı" kodunuzu seri koda çevirmeye eğilimlidir. (Bunun düzeltilmesi sadece kodu yavaşlatır ve şişirir.) STM veya aktör modelleri gibi kilit tabanlı iş parçacığının, en düşük düzey kod kötüden başka bir şeyde kullanılmasını sağlayan iyi alternatifler vardır.
SADECE benim doğru görüşüm

"neden zincir" :) zarar verebilir 3 adımdan sonra durduğunuzdan emin olun.
szymanowski

27

Tam olarak ne olduğunu tam olarak bildiğiniz sürece Eval iyidir . Herhangi bir kullanıcı girişi kontrol edilmeli ve doğrulanmalıdır ZORUNLU ve her şey. Nasıl% 100 emin olacağınızı bilmiyorsanız, yapmayın.

Temel olarak, bir kullanıcı söz konusu dil için herhangi bir kod yazabilir ve yürütülür. Ne kadar hasar verebileceğini kendiniz hayal edebilirsiniz.


1
Yani eğer doğrudan kullanıcı girişini kopyalamayacak bir algoritma kullanarak kullanıcı girişine dayalı S-ifadeleri üretiyorsam ve bu bazı durumlarda makrolar veya diğer teknikler kullanmaktan daha kolay ve netse, o zaman hiçbir şey "kötülük" " hakkında? Başka bir deyişle, eval ile ilgili tek sorun SQL sorguları ve doğrudan kullanıcı girişini kullanan diğer tekniklerle aynı mıdır?
Jay

10
"Kötülük" olarak adlandırılmasının nedeni, yanlış yapmanın, diğer şeyleri yanlış yapmaktan çok daha kötü olmasıdır. Ve bildiğimiz gibi, yeni başlayanlar bir şeyler yanlış yapacak.
Tor Valamo

3
Her koşulda değerlendirmeden önce kodun doğrulanması gerektiğini söylemem. Örneğin basit bir REPL uygularken, girdiyi muhtemelen kontrol edilmemiş olarak eval'e beslersiniz ve bu bir sorun olmazdı (elbette web tabanlı bir REPL yazarken bir korumalı alana ihtiyacınız olacak, ancak bu normal değil Kullanıcının sisteminde çalışan CLI-REPL'ler).
sepp2k

1
Dediğim gibi, eval beslemek ne beslemek tam olarak ne olduğunu bilmek zorunda. Bu, "sanal alan sınırları dahilinde bazı komutları yürütecek" anlamına gelirse, bunun anlamı budur. ;)
Tor Valamo

@TorValamo hapishaneyi hiç duydunuz mu?
Loïc Faure-Lacroix

21

"Ne zaman kullanmalıyım eval?" daha iyi bir soru olabilir.

Kısa cevap "programınız çalışma zamanında başka bir program yazıp daha sonra çalıştırmayı amaçladığında" dır. Genetik programlama , kullanılması muhtemel olan bir duruma bir örnektir eval.


14

IMO, bu soru LISP'ye özgü değildir . İşte PHP için aynı soruya bir cevap ve LISP, Ruby ve bir değerlendirme olan diğer dil için geçerlidir:

Eval () ile ilgili temel sorunlar şunlardır:

  • Potansiyel güvensiz giriş. Güvenilmeyen bir parametreyi iletmek başarısız olmanın bir yoludur. Bir parametrenin (veya bir kısmının) tamamen güvenilir olduğundan emin olmak genellikle önemsiz bir görev değildir.
  • Trickyness. Eval () kullanmak kodu akıllı yapar, bu nedenle takip edilmesi daha zordur. Brian Kernighan'dan alıntı yapmak için " Hata ayıklama ilk etapta kodu yazmaktan iki kat daha zordur. Bu nedenle, kodu olabildiğince akıllıca yazarsanız, tanımı gereği hata ayıklamak için yeterince akıllı değilsiniz "

Eval () 'nin gerçek kullanımı ile ilgili temel sorun sadece birdir:

  • yeterince dikkate almadan kullanan deneyimsiz geliştiriciler.

Buradan alındı .

Aldatıcılık parçası inanılmaz bir nokta. Kod golf ve özlü kod takıntısı her zaman "zeki" kod ile sonuçlandı (hangi için evals harika bir araçtır). Ancak okunabilirlik için IMO kodunuzu yazmalısınız, zeki olduğunuzu göstermemeli ve kağıt tasarrufu yapmamalısınız (yine de yazdırmayacaksınız).

Daha sonra LISP'de, eval'ün çalıştırıldığı bağlamla ilgili bazı problemler vardır, böylece güvenilmeyen kod daha fazla şeye erişebilir; bu sorun zaten yaygın gibi görünüyor.


3
EVAL ile ilgili "kötü girdi" sorunu yalnızca Lisp dışı dilleri etkiler, çünkü bu dillerde eval () genellikle bir dize argümanı alır ve kullanıcının girdisi genellikle eklenir. Kullanıcı girdilerine bir alıntı ekleyebilir ve oluşturulan kod. Ancak Lisp'te EVAL'nin argümanı bir dize değildir ve kesinlikle pervasız değilseniz kullanıcı girişi koda kaçamaz (daha sonra dahil edeceğiniz bir S ifadesi oluşturmak için girdiyi READ-FROM-STRING ile ayrıştırdığınız gibi) alıntı yapmadan EVAL kodu. Alıntı yaparsanız, alıntı kaçmak için hiçbir yolu yoktur).
Deplasman Hesabı

12

Çok büyük cevaplar oldu, ancak burada Racket uygulayıcılarından Matthew Flatt'tan başka bir cevap var:

http://blog.racket-lang.org/2011/10/on-eval-in-dynamic-languages-generally.html

Zaten kapsanan noktaların çoğunu yapar, ancak bazı insanlar yine de ilginç olduğunu düşünebilir.

Özet: Kullanıldığı bağlam, değerlendirmenin sonucunu etkiler, ancak programcılar tarafından genellikle dikkate alınmaz ve bu da beklenmedik sonuçlara yol açar.


11

Kanonik cevap uzak durmaktır. Hangi garip buluyorum, çünkü bu bir ilkel ve yedi ilkelden (diğerleri eksileri, araba, cdr, eğer, eq ve alıntı), en az miktarda kullanım ve sevgi alıyor.

Gönderen On Lisp : "Genellikle, açıkça eval çağırarak havaalanı hediye dükkanında bir şey satın gibidir son dakikaya kadar bekledi olması, ikinci sınıf malların sınırlı seçimi için yüksek fiyatlar ödemek zorunda."

Peki eval'i ne zaman kullanırım? Normal kullanımlardan biri, değerlendirerek REPL'inizde bir REPL bulundurmaktır (loop (print (eval (read)))). Herkes bu kullanımda iyi.

Ancak, eval ile backquote'u birleştirerek işlevleri derleme sonrasında değerlendirilecek makrolar olarak da tanımlayabilirsiniz . Git

(eval `(macro ,arg0 ,arg1 ,arg2))))

ve sizin için bağlamı öldürecektir.

Swank (emacs slime için) bu vakalarla doludur. Şuna benziyorlar:

(defun toggle-trace-aux (fspec &rest args)
  (cond ((member fspec (eval '(trace)) :test #'equal)
         (eval `(untrace ,fspec))
         (format nil "~S is now untraced." fspec))
        (t
         (eval `(trace ,@(if args `(:encapsulate nil) (list)) ,fspec ,@args))
         (format nil "~S is now traced." fspec))))

Bunun pis bir hack olduğunu düşünmüyorum. Makroları işlevlere yeniden entegre etmek için her zaman kendim kullanıyorum.


1
Çekirdek dilini kontrol etmek isteyebilirsiniz;)
artemonster

7

Lisp eval ile ilgili bir kaç nokta daha:

  • Yerel bağlamınızı kaybederek küresel ortamda değerlendirir.
  • Bazen '#' okuma makrosunu gerçekten kullanmak istediğinizde eval kullanmaya cazip gelebilirsiniz. Bu, okuma zamanında değerlendirilir.

Küresel env kullanımının hem Common Lisp hem de Scheme için geçerli olduğunu anlıyorum; Clojure için de geçerli mi?
Jay

2
Şemada (en azından R7RS için, belki de R6RS için) değerlendirmek için bir ortam geçmelisiniz.
csl

4

GOTO "kuralı" gibi: Ne yaptığınızı bilmiyorsanız, karışıklık yapabilirsiniz.

Yalnızca bilinen ve güvenli verilerden bir şeyler oluşturmanın yanı sıra, bazı dillerin / uygulamaların kodu yeterince optimize edememesi sorunu vardır. Sonunda içinde yorumlanmış kod bulunabilir eval.


Bu kuralın GOTO ile ne ilgisi var? Hangi ile herhangi bir programlama dilinde herhangi bir özellik var mıdır edemez bir karmaşa?
Ken

2
@Ken: GOTO kuralı yok, bu yüzden cevabımdaki tırnak işaretleri. Kendileri için düşünmekten korkan insanlar için sadece bir dogma var. Eval için aynı. Eval kullanarak bazı Perl komut dosyası dramatik hızlandırmak hatırlıyorum. Araç kutunuzdaki bir araç. Diğer dil yapıları daha kolay / daha iyi olduğunda yeni başlayanlar genellikle eval kullanır. Ama sadece serin olmak ve lütfen dogmatik insanları bundan kaçınmak?
stesch

4

Eval güvenli değildir. Örneğin, aşağıdaki kodunuz var:

eval('
hello('.$_GET['user'].');
');

Şimdi kullanıcı sitenize geliyor ve URL giriyor http://example.com/file.php?user= ); $ is_admin = true; echo (

Sonra ortaya çıkan kod şöyle olacaktır:

hello();$is_admin=true;echo();

6
Lisp düşünce php hakkında konuşuyordu
fmsf

4
@fmsf Özellikle Lisp hakkında ama genel olarak evalbu dilde konuşuyordu .
Skilldrick

4
@fmsf - bu aslında dilden bağımsız bir soru. Statik derlenmiş diller için bile geçerlidir, çünkü çalışma zamanında derleyiciyi çağırarak değerlendirmeyi simüle edebilirler.
Daniel Earwicker

1
bu durumda dil bir kopyadır. Burada böyle bir sürü gördüm.
fmsf

9
PHP eval Lisp eval gibi değil. Bakın, bir karakter dizesinde çalışır ve URL'deki istismar, metinsel bir parantezin kapatılıp başka bir parola açılmasına bağlıdır. Lisp eval bu tür şeylere açık değildir. Bir ağdan girdi olarak gelen verileri, doğru şekilde sanallaştırırsanız (ve yapı bunu yapmak için yürüyecek kadar kolaysa) değerlendirebilirsiniz.
Kaz

2

Eval kötü değildir. Eval karmaşık değildir. Bu, kendisine ilettiğiniz listeyi derleyen bir işlevdir. Diğer birçok dilde, rastgele kod derlemek, dilin AST'sini öğrenmek ve derleyici API'sini bulmak için derleyici iç kısımlarını araştırmak anlamına gelir. Lisp'de sadece eval diyoruz.

Ne zaman kullanmalısın? Bir şeyi derlemeniz gerektiğinde, genellikle çalışma zamanında rasgele kod kabul eden, üreten veya değiştiren bir program .

Ne zaman kullanmamalısınız? Diğer tüm durumlar.

İhtiyacınız olmadığında neden kullanmıyorsunuz? Çünkü okunabilirlik, performans ve hata ayıklama için sorunlara neden olabilecek gereksiz karmaşık bir şekilde bir şeyler yapıyorsunuz.

Evet, ama yeni başlayan biriysem kullanmam gerektiğini nasıl anlarım? İhtiyacınız olanı her zaman fonksiyonlarla uygulamaya çalışın. Bu işe yaramazsa makro ekleyin. Bu hala işe yaramazsa, o zaman değerlendirin!

Bu kurallara uyun ve eval ile asla kötülük yapmazsınız :)


0

Zak'ın cevabını çok seviyorum ve konunun özüne geldi: eval , yeni bir dil, bir senaryo veya bir dilde değişiklik yaparken kullanılır. Gerçekten daha fazla açıklamıyor, bu yüzden bir örnek vereceğim:

(eval (read-line))

Bu basit Lisp programında, kullanıcıdan girdi istenir ve sonra girdikleri her şey değerlendirilir. Bunun çalışması için , program derlenmişse sembol tanımlarının tüm setinin mevcut olması gerekir, çünkü kullanıcının hangi işlevleri girebileceği hakkında hiçbir fikriniz yoktur, bu yüzden hepsini dahil etmeniz gerekir. Bu, bu basit programı derlerseniz, sonuçta ortaya çıkan ikili program devasa olacaktır.

İlke olarak, bu nedenle bu derlenebilir bir ifadeyi bile düşünemezsiniz. Genel olarak, eval kullandıktan sonra , yorumlanmış bir ortamda çalışıyorsunuz ve kod artık derlenemez. Eval kullanmazsanız , bir C programı gibi bir Lisp veya Scheme programını derleyebilirsiniz. Bu nedenle, kullanmaya başlamadan önce yorumlanmış bir ortamda olmak istediğinizden ve ihtiyaç duyduğunuzdan emin olmak istersiniz. eval .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.